specter-desktop/src/cryptoadvance/specter/templates/includes/tx-table.html
kdmukai 4143cb88aa
Babel integration (#1247)
* Default bitcoind timeout to 60s for all platforms

* Create bitcoind_setup_tasks.py

* Missing prop setter bugfix

Fixes #1221

* Early progress check-in

* Docs update, initial FR translation file

* Lots more text wrapping, babel.cfg fix for html

* docs update

* Updated translator procedures, added initial German, Hebrew, and Portugese files

* Update README.md

* Update README.md

* Update messages.po

Started the translation process using Poedit. Lines

* Test first modif in ES

First modification (only adding one line here) using Poedit and following the translation steps given by kdmukai.

If everything is working well here I am ready to start translating.

* Update messages.po

* Bugfix; multi-language mnemonic support

* Explicit language selection

* Translation support for strings with variable insertions

* Bugfixes; define supported langages in only one place

* Cleanup

* Update messages.po

* Update messages.po

* Latest translations integrated

* Regenerating messages; disabling fuzzy matching

* Compiled messages

* template bugfixes

* Use jinja's "tojson" to safely escape account_map device labels

* Update wallet_pdf.jinja

* Update helpers.py

* Update node.py

* Bugfixes

* Updated requirements.txt; bugfix on language selection on Settings

* Update wallets.py

* Require `mnemonic==0.19` for multi-language BIP39 wordlists

* Update iw translation

* "he" language code fix for Hebrew

* Update README.md

* Adding blank Greek file; minor README update

* Update messages.po

50% on this ... My ass is flat.

* Spanish translation file updated

* Update messages.po

* Extracted from the latest code; recompiled with the latest translations

* Added missing wrapped strings

* Update messages.po

* Updated Spanish translations

* Adding blank Dutch ("nl") translation file

* Blank Korean ("ko") translation file

* Blank Portugese ("pt") file updated

* Blank Turkish ("tr") translation file

* Adding blank Serbian ("sr") translation file

* Update messages.po

* Compiled latest Spanish translations

* Translation progress

Translated the first 181 strings

* Blank Italian ("it") translation file

* More translation work done

6667810 strings translated. Nice number, guess I'll stop here :)

* Compiled first batch of German translations

* Update messages.po

* Translation 100%

All done. Would be extra pleased if my name popped up somewhere in credits. Not required though :)

* Update messages.po

* Update messages.po

All done !!! :)

* Compiled in initial Greek translations

* Updated completion of German translation

* Initial Dutch translations compiled in

* Compiled full Spanish translations

* Re-ran extraction, update, compile

* Update Dutch translation

* Blank Icelandic ("is") translation file

* Blank Chinese (zh_Hans_CN and zh_hant_TW) translation files

* Compiled latest Dutch translations

* Add missing strings to translation files

* Blank Swedish (sv) translation file

* Blank Polish ("pl") translation file

* Blank Bulgarian ("bg") translation file

* Blank Russian ("ru") translation file

* Swedish 30% done

* Update messages.po

* Blank Croatian ("hr") translation file

* Delete messages.mo

* Added translator

Also fixed some warnings in poedit

* Babel docs update

* Compiled latest Dutch and Swedish translations

* Update messages.po

Completed 100%

* Update wallets.py

* Compiled in latest German translations

* Update messages.po

* Extracted, updated, compiled translation files

* Compiled in first batch of Polish translations

* Update messages.po

More translations.

* Compiled latest Polish translations

* Hindi, Tamil, Malayalam translation files

* Completed Swedish translation

* 80% complete

* Blank Finnish ("fi") translation file

* Compiled latest Swedish translations

* Full update and recompile of Swedish

* Compiled Polish translations

* Full update and recompile for Polish

* Update messages.po

* Updating Poedit docs

* Removed word wrap; compiled latest Dutch

* Hindi Translation --initial

* updated w/no line wrap; compiled first Hindi translations

* Update messages.po

* Update messages.po

Initial and partial test Bulgarian commit

* updated and compiled initial Bulgarian translations.

* Bugfix on text wrapping

* Update messages.po

* Update messages.po

BG translation ~25% complete

* Fully completed polish translation (first draft)

Probably needs some further tweaking here and there.

* Start Russian translation

* Russian translation 30% complete

* Format fixes

* Compiled latest French translations

* Compiled latest Chinese (Traditional) translations

* Compiled latest Bulgarian translations

* Compiled latest Polish translations

* Compiled latest Russian translations

* Update French to 36%

* Update messages.po

* Compiled in latest French translations

* Compiled latest Dutch translations

* Russian 70% complete

* Update messages.po

* Russian 100% complete

* Update French to 44%

Slowly but surely

* Adds in-progress languages; latest Russian translations compiled

* Extracted latest; updated Russian translation file.

* Compiled latest Chinese (Traditional) translations

* Updated and compiled Chinese (Traditional)

* Compiled latest Russian translations

* Updated and compiled latest French translations

* Updated all translation files

* Fix double quotes format issue

Co-authored-by: Kim Neunert <k9ert@gmx.de>
Co-authored-by: 2a3dex <58116837+2a3dex@users.noreply.github.com>
Co-authored-by: rafa1239 <raphael.altieri@gmail.com>
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Co-authored-by: Volker Herminghaus <volker@bitcorn.wtf>
Co-authored-by: glowleaf <69010742+glowleaf@users.noreply.github.com>
Co-authored-by: DirkVdk <86686185+DirkVdk@users.noreply.github.com>
Co-authored-by: mutatrum <mutatrum@gmail.com>
Co-authored-by: Kryptoministern <77808545+Kryptoministern@users.noreply.github.com>
Co-authored-by: PommbearBTC <86745004+PommbearBTC@users.noreply.github.com>
Co-authored-by: Gummybear <holuan@gmail.com>
Co-authored-by: sreshta suresh <sreshtasuresh@gmail.com>
Co-authored-by: KST-Energy <82534243+KST-Energy@users.noreply.github.com>
Co-authored-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
Co-authored-by: dudezoo <86764634+dudezoo@users.noreply.github.com>
Co-authored-by: Sergei Tikhomirov <sergey.s.tikhomirov@gmail.com>
Co-authored-by: Bitpaint <67663265+bitpaint@users.noreply.github.com>
2021-07-05 17:14:19 -05:00

698 lines
31 KiB
HTML

<template id="tx-table">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
<style>
.tx-table {
display: table;
}
tx-row {
display: contents;
}
.pagination-container {
text-align: center;
width: 100%;
}
.pagination-next, .pagination-back {
cursor: pointer;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
.pagination-idx {
min-width: 50px;
max-width: 50px;
width: 50px;
margin: 7px 7px 7px 3px;
text-align: center;
height: 35px;
}
.search {
/* float: right; */
margin: auto;
width: 100%;
max-width: 350px;
}
.search-container {
width: 100%;
display: flex;
margin-bottom: 0.5em;
}
.up-arrow {
width: 0;
height: 0;
border: solid 5px transparent;
background: transparent;
border-bottom: solid 7px #fff;
border-top-width: 0;
cursor: pointer;
float: right;
margin-right: 50px;
margin-top: 5px;
}
.down-arrow {
width: 0;
height: 0;
border: solid 5px transparent;
background: transparent;
border-top: solid 7px #fff;
border-bottom-width: 0;
margin-top:1px;
cursor: pointer;
float: right;
margin-right: 50px;
margin-top: 5px;
}
.export-container {
max-width: 50%;
margin: 20px auto;
border: 1px solid #fff;
padding: 20px;
text-align: center;
border-radius: 7px;
}
.export {
max-width: 200px;
margin: auto;
}
.selected-rows-action-box {
float: right;
}
.selected-rows-action-box .btn {
width: 200px;
margin: 5px;
height: 38px;
}
</style>
<nav class="row">
<button
type="button"
class="txlist-view-btn btn radio left">
{{ _("History") }}
</button>
<button
type="button"
class="utxo-view-btn btn radio right">
UTXO
</button>
</nav>
<div class="pagination-container">
<a class="pagination-back pagination-arrow pagination-disabled">&#8249;</a>
{{ _("Page") }} <input class="pagination-idx" type="number" step="1" min="1" value="1"></input><span class="pagination-counter"></span>
<a class="pagination-next pagination-arrow pagination-disabled">&#8250;</a>
<br>
</div>
<div class="search-container">
<!--Left part of the toolbar-->
<div style="flex: 1;">
{% include "includes/page-limit-select.html" %}
</div>
<!--Middle part of the toolbar-->
<input class="search" type="text" placeholder="Search..." />
<!--Placeholder for right part of the toolbar-->
<div style="flex: 1;"></div>
</div>
<div class="selected-rows-action-box row hidden">
<form class="freeze-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn freeze-tx-btn">
<tool-tip title="Freezing UTXOs">
{{ _("Freezing a UTXO will make Specter disallow spending it.") }}<br><br>
{{ _("This is useful especially for dealing with a dust attack, where you don't want to accidentally spend the dust received.") }}"
</tool-tip>&nbsp;&nbsp;
Un/Freeze UTXOs
</button>
</form>
<form class="compose-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn compose-tx-btn">{{ _("Create new transaction") }}</button>
</form>
</div>
<div class="export-container hidden">
<h1>{{ _("Export transactions to CSV") }}</h1>
<div class="{% if not specter.price_check or not (specter.alt_rate and specter.alt_symbol) %}hidden{% endif %}">
{{ _("Fetch historical price data") }}:
<label class="switch">
<input type="checkbox" class="export-prices">
<span class="slider"></span>
</label><br><br>
<div class="note">
{{ _("If enabled, specter will try to fetch historical prices for each transaction date from the configured price provider.") }}
<br><b>{{ _("This has privacy implications!") }}</b>
</div><br>
</div>
<a class="export btn" download>{{ _("Export") }}</a>
</div>
<table class="tx-table">
<thead>
<tr>
<th value="category" class="category-header"><div class="category-arrow"></div></th>
<th value="txid" class="optional txid-header">TxID<div class="txid-arrow"></div></th>
<th value="label" class="optional label-header">{{ _("Label") }}<div class="label-arrow"></div></th>
<th value="amount" class="amount-header">{{ _("Amount") }}<div class="amount-arrow"></div></th>
<th value="time" class="time-header">{{ _("Time") }}<div class="time-arrow up-arrow"></div></th>
<th value="confirmations" class="confirmations-header">{{ _("Confirmations") }}<div class="confirmations-arrow"></div></th>
<th value="blockhash" class="optional hidden blockhash-header">
{% include "includes/merkletooltip.html" %}
{{ _("Block Hash") }}
</th>
<th style="width: 50px; padding: 10px;"><button type="button" class="export-btn btn" style="width: 50px;">{{ _("Export") }}</button></th>
</tr>
</thead>
<tr class="empty">
<td></td>
<td>{{ _("Fetching transactions...") }}</td>
<td class="optional"></td>
<td class="optional"></td>
<td></td>
<td></td>
<td class="optional hidden blockhash-empty"></td>
<td></td>
</tr>
<tr class="summary-row hidden">
<td></td>
<td class="summary-tx-count"></td>
<td class="optional"></td>
<td class="summary-amount"></td>
<td class="optional"></td>
<td></td>
<td class="optional hidden blockhash-summary"></td>
<td></td>
</tr>
<tbody class="tx-tbody">
</tbody>
</table>
</template>
<script type="text/javascript">
/**
* Custom element for showing a table of transactions.
* Can be configured to show:
* - The transaction history of a wallet (set attribute `type` to "txlist" and `wallet` to the wallet's alias)
* - The UTXO list of a wallet (set attribute `type` to "utxo" and `wallet` to the wallet's alias)
* - The transaction history of all the user's wallets (set attribute `type` to "txlist" and `wallet` to null)
* - The UTXO list of all the user's wallets (set attribute `type` to "utxo" and `wallet` to null)
* The uses Specter's API to fetch transactions every time the user changes its parameters.
* These parameters are configured using components included in this table element:
* - Tabs for switching between History (`type="txlist"`) and UTXO (`type="utxo"`) list types
* - Sorting transactions by any column (ascending or descending)
* - Search bar to filter transactions using free text search
* - Configuring limit for transactions displayed per page
* - Pagination, jumping between pages and showing the total pages count
* - Export - export the full (unfiltered) list from which the table is drawing its data as CSV (optionally with price history)
*
* The table also supports:
* - Showing amounts in either BTC or sats (set attribute `btc-unit` to either "btc" or "sat")
* - Showing prices next to the amounts (set attribute `price` to the BTC price and symbol to the symbol of the currency you're pricing at)
* - Showing validated blockhash column (set attribute `blockhash` to either "true" or "false")
*/
class TxTableElement extends HTMLElement {
constructor() {
super();
// Create a shadow root
var shadow = this.attachShadow({mode: 'open'});
var style = document.getElementById('tx-table').content;
var clone = style.cloneNode(true);
this.el = clone.querySelector(".tx-table");
this.tbody = clone.querySelector(".tx-tbody");
// Empty row element
this.emptyRow = clone.querySelector(".empty");
// Blockhash header and empty column
this.blockhashHeader = clone.querySelector(".blockhash-header");
this.blockhashEmpty = clone.querySelector(".blockhash-empty");
this.blockhashSummary = clone.querySelector(".blockhash-summary");
// Tabs (History/ UTXO)
this.txlistViewBtn = clone.querySelector(".txlist-view-btn");
this.utxoViewBtn = clone.querySelector(".utxo-view-btn");
// Pagination
this.paginationNext = clone.querySelector(".pagination-next");
this.paginationBack = clone.querySelector(".pagination-back");
this.paginationContainer = clone.querySelector(".pagination-container");
this.paginationCounter = clone.querySelector(".pagination-counter");
this.paginationIdxInput = clone.querySelector(".pagination-idx");
this.pageLimitSelect = clone.querySelector(".page-limit-select");
// Search bar element
this.searchInput = clone.querySelector(".search");
// Sorting headers and arrows
this.categoryHeader = clone.querySelector(".category-header");
this.txidHeader = clone.querySelector(".txid-header");
this.labelHeader = clone.querySelector(".label-header");
this.amountHeader = clone.querySelector(".amount-header");
this.timeHeader = clone.querySelector(".time-header");
this.confirmationsHeader = clone.querySelector(".confirmations-header");
this.categoryArrow = clone.querySelector(".category-arrow");
this.txidArrow = clone.querySelector(".txid-arrow");
this.labelArrow = clone.querySelector(".label-arrow");
this.amountArrow = clone.querySelector(".amount-arrow");
this.timeArrow = clone.querySelector(".time-arrow");
this.confirmationsArrow = clone.querySelector(".confirmations-arrow");
// Summary row
this.summaryRow = clone.querySelector(".summary-row");
this.summaryTxCount = clone.querySelector(".summary-tx-count");
this.summaryAmount = clone.querySelector(".summary-amount");
// Export button
this.exportBtn = clone.querySelector(".export-btn");
this.exportContainer = clone.querySelector(".export-container");
this.export = clone.querySelector(".export");
this.exportPricesSwitch = clone.querySelector(".export-prices");
// Selected rows
this.selectedRowsActionBox = clone.querySelector(".selected-rows-action-box");
this.composeTxForm = clone.querySelector(".compose-tx-form");
this.composeTxBtn = clone.querySelector(".compose-tx-btn");
this.freezeTxForm = clone.querySelector(".freeze-tx-form");
this.freezeTxBtn = clone.querySelector(".freeze-tx-btn");
this.selectedRows = [];
this.txlistViewBtn.onclick = () => {
if (!this.txlistViewBtn.classList.contains("checked")) {
this.utxoViewBtn.classList.remove("checked");
this.txlistViewBtn.classList.add("checked");
this.setAttribute("type","txlist");
}
}
this.utxoViewBtn.onclick = () => {
if (!this.utxoViewBtn.classList.contains("checked")) {
this.txlistViewBtn.classList.remove("checked");
this.utxoViewBtn.classList.add("checked");
this.setAttribute("type","utxo");
}
}
// Init call id to avoid fetch returning after another one triggered
this.callId = 0
// Initialize default sort, search, limit and page parameters
this.idx = 0;
this.limit = 50;
this.search = "";
this.sortby = "time";
this.sortdir = "desc";
this.pageCount = 1;
// Next page action
this.paginationNext.onclick = () => {
this.idx++;
fetchTxItems(this);
}
// Previous page action
this.paginationBack.onclick = () => {
this.idx--;
fetchTxItems(this);
}
// Listen to changes in page number selection input
this.paginationIdxInput.onchange = () => {
let newIdx = parseInt(this.paginationIdxInput.value);
if (isNaN(newIdx) || (newIdx < 1 || newIdx > this.pageCount)) {
this.paginationIdxInput.value = this.idx + 1;
return;
}
this.idx = newIdx - 1;
fetchTxItems(this);
}
// Listen to changes in search bar
this.searchInput.onchange = () => {
this.search = this.searchInput.value;
this.export.href = this.export.href.split('?')[0] + `?exportPrices=${this.exportPricesSwitch.checked}&search=${this.search}&sortby=${this.sortby}&sortdir=${this.sortdir}`;
fetchTxItems(this);
}
// Listen to changes in page limit
this.pageLimitSelect.onchange = () => {
this.limit = this.pageLimitSelect.value;
fetchTxItems(this);
}
// Setup sorting functionality for each column of the table header
let sortHeaders = [
this.categoryHeader,
this.txidHeader,
this.labelHeader,
this.amountHeader,
this.timeHeader,
this.confirmationsHeader
];
for (let header of sortHeaders) {
header.onclick = () => {
if (header.classList.toString().indexOf(this.sortby) != -1) {
// flip direction
this.el.querySelector(`.${this.sortby}-arrow`).classList.remove(this.sortdir == 'asc' ? 'down-arrow' : 'up-arrow');
this.el.querySelector(`.${this.sortby}-arrow`).classList.add(this.sortdir == 'asc' ? 'up-arrow' : 'down-arrow');
this.sortdir = this.sortdir == 'asc' ? 'desc' : 'asc';
this.export.href = this.export.href.split('?')[0] + `?exportPrices=${this.exportPricesSwitch.checked}&search=${this.search}&sortby=${this.sortby}&sortdir=${this.sortdir}`;
} else {
sortHeaders.forEach(el => {
let existingArrow = el.querySelector(`.${this.sortby}-arrow`)
if (existingArrow) {
existingArrow.classList.remove('up-arrow');
existingArrow.classList.remove('down-arrow');
}
});
this.sortby = header.getAttribute('value');
this.el.querySelector(`.${this.sortby}-arrow`).classList.add(this.sortdir == 'asc' ? 'down-arrow' : 'up-arrow');
this.export.href = this.export.href.split('?')[0] + `?exportPrices=${this.exportPricesSwitch.checked}&search=${this.search}&sortby=${this.sortby}&sortdir=${this.sortdir}`;
}
fetchTxItems(this);
}
}
// Export data
this.exportBtn.onclick = () => {
if (this.exportBtn.innerText == 'Done') {
this.exportContainer.classList.add('hidden');
this.exportBtn.innerText = 'Export';
} else {
this.exportContainer.classList.remove('hidden');
this.exportBtn.innerText = 'Done';
}
}
this.export.onclick = () => {
this.exportContainer.classList.add('hidden');
this.exportBtn.innerText = 'Export';
}
// Export prices switch toggle
this.exportPricesSwitch.onchange = () => {
this.export.href = this.export.href.split('?')[0] + `?exportPrices=${this.exportPricesSwitch.checked}&search=${this.search}&sortby=${this.sortby}&sortdir=${this.sortdir}`;
}
this.composeTxBtn.onclick = () => {
for (let tx of this.selectedRows) {
this.composeTxForm.innerHTML += `
<input type="checkbox" checked class="hidden" name="coinselect" value="${tx.txid}, ${tx.vout}">
`
}
this.composeTxForm.action = `{{ url_for('wallets_endpoint.send_new', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", this.wallet);
this.composeTxForm.submit();
}
this.freezeTxBtn.onclick = () => {
for (let tx of this.selectedRows) {
this.freezeTxForm.innerHTML += `
<input type="checkbox" checked class="hidden" name="selected_utxo" value="${tx.txid}:${tx.vout}">
`
}
this.freezeTxForm.innerHTML += `<input type="hidden" name="action" value="freezeutxo"/>`
this.freezeTxForm.action = `{{ url_for('wallets_endpoint.history', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", this.wallet);
this.freezeTxForm.submit();
}
// Attach the created element to the shadow dom
shadow.appendChild(clone);
}
static get observedAttributes() {
return ['blockhash', 'btc-unit', 'price', 'symbol', 'type', 'hide-sensitive-info', 'wallet'];
}
/**
* Listens to changes on the following attributes:
* - blockhash: Should show blockhash column. Either "true" or "false"
* - btc-unit: Bitcoin unit to display amounts with. Either "btc" or "sat"
* - price: BTC price for price calculations
* - symbol: Currency symbol for price calculations
* - type: Transactions list type to load. Either "txlist" or "utxo"
* - hide-sensitive-info: Mask user sensitive info. Either "true" or "false"
* - wallet: The wallet alias (null to get all wallets combined)
*/
attributeChangedCallback(attrName, oldValue, newValue) {
if (this.getAttribute('blockhash') == 'true' && this.getAttribute('type') == "txlist" && this.getAttribute('wallet')) {
this.blockhashHeader.classList.remove('hidden');
this.blockhashEmpty.classList.remove('hidden');
this.blockhashSummary.classList.remove('hidden');
} else {
this.blockhashHeader.classList.add('hidden');
this.blockhashEmpty.classList.add('hidden');
this.blockhashSummary.classList.add('hidden');
}
if (
this.blockhash != this.getAttribute('blockhash') ||
this.btcUnit != this.getAttribute('btc-unit') ||
this.price != this.getAttribute('price') ||
this.symbol != this.getAttribute('symbol') ||
this.listType != this.getAttribute('type') ||
this.hideSensitiveInfo != this.getAttribute('hide-sensitive-info') ||
this.wallet != this.getAttribute('wallet')
) {
this.blockhash = this.getAttribute('blockhash');
this.btcUnit = this.getAttribute('btc-unit');
this.price = this.getAttribute('price');
this.symbol = this.getAttribute('symbol');
this.listType = this.getAttribute('type');
this.hideSensitiveInfo = this.getAttribute('hide-sensitive-info') == 'true';
this.wallet = this.getAttribute('wallet');
if (!this.listType) {
return
}
fetchTxItems(this);
}
}
}
/**
* Fetches txlist from the Specter API and loads the result into TxRowElement
* @param self - The TxTableElement itself.
*/
async function fetchTxItems(self) {
self.callId++;
// Clean up from existing transaction list
self.summaryRow.classList.add('hidden');
self.emptyRow.children[1].innerText = '{{ _("Fetching transactions...") }}';
self.emptyRow.classList.remove('hidden');
self.tbody.querySelectorAll('tx-row').forEach((row) => {row.remove()});
if (!self.listType) {
self.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }}';
return
}
if (self.listType == "txlist") {
self.txlistViewBtn.classList.add("checked");
} else {
self.utxoViewBtn.classList.add("checked");
}
// Select the correct URL based on the list type and selected wallet
let url;
switch (self.listType) {
case "txlist":
if (self.wallet) {
self.export.href = `{{ url_for('wallets_endpoint.tx_history_csv', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
url = `{{ url_for('wallets_endpoint.txlist', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
} else {
self.export.href = `{{ url_for('wallets_endpoint.wallet_overview_txs_csv') }}`;
url = `{{ url_for('wallets_endpoint.wallets_overview_txlist') }}`;
}
break;
case "utxo":
if (self.wallet) {
self.export.href = `{{ url_for('wallets_endpoint.utxo_csv', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
url = `{{ url_for('wallets_endpoint.utxo_list', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
} else {
self.export.href = `{{ url_for('wallets_endpoint.wallet_overview_utxo_csv') }}`;
url = `{{ url_for('wallets_endpoint.wallets_overview_utxo_list') }}`;
}
break
default:
self.export.href = ``
self.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }} ';
return
}
if (self.export.href != '') {
self.export.href += `?exportPrices=${self.exportPricesSwitch.checked}&search=${self.search}&sortby=${self.sortby}&sortdir=${self.sortdir}`;
}
// Prepare form data with all relevant parameters
var formData = new FormData();
formData.append('idx', self.idx);
formData.append('limit', self.limit);
formData.append('search', self.search);
formData.append('sortby', self.sortby);
formData.append('sortdir', self.sortdir);
formData.append('csrf_token', '{{ csrf_token() }}');
try {
let callId = self.callId;
const response = await fetch(
url,
{
method: 'POST',
body: formData
}
);
if (callId != self.callId) {
return;
}
if(response.status != 200){
showError(await response.text());
return;
}
const jsonResponse = await response.json();
let showBlockhash = !self.blockhashHeader.classList.contains('hidden');
if ("txlist" in jsonResponse) {
// Populate the table with TXRowElement objects using the fetched txlist
if (jsonResponse.txlist) {
let txlist = JSON.parse(jsonResponse.txlist);
let summaryTxCount = 0;
let summaryAmount = 0;
let summaryPrice = 0;
for (let tx of txlist) {
let txRow = document.createRange().createContextualFragment(`
<tx-row
data-btc-unit="${self.btcUnit ? self.btcUnit : 'btc'}"
data-price="${self.price ? self.price : 0}"
data-symbol="${self.symbol ? self.symbol : ''}"
data-tx='${JSON.stringify(tx).replace(/[\(]/g, "&lpar;").replace(/[\)]/g, "&rpar;").replace(/[\/]/g, "&sol;").replace(/[\']/g, "&apos;")}'
data-wallet="${(self.wallet ? self.wallet : tx.wallet_alias)}"
data-show-blockhash="${showBlockhash}"
data-mode="${self.getAttribute('type') + (self.wallet ? "" : "-overview")}"
data-hide-sensitive-info="${self.hideSensitiveInfo}">
</tx-row>
`)
self.tbody.append(txRow);
summaryTxCount++;
summaryAmount += parseFloat(self.tbody.children[self.tbody.children.length - 1].shadowRoot.querySelector('.amount').innerText.replace(',', ''));
summaryPrice += parseFloat(self.tbody.children[self.tbody.children.length - 1].shadowRoot.querySelector('.amount-price').innerText.replace(`(${self.symbol}`, '').replace(')', '').replace(',', ''));
}
if (summaryTxCount > 0 && self.search) {
self.summaryTxCount.innerText = `Transactions count: ${summaryTxCount}`;
self.summaryAmount.innerHTML = `Total amount: ${numberWithCommas(summaryAmount.toFixed(8))}`;
if (summaryPrice) {
self.summaryAmount.innerHTML += ` <span class="note">(${self.symbol}${numberWithCommas(summaryPrice.toFixed(2))})</span>`
}
self.summaryRow.classList.remove('hidden');
}
// Pagination
self.pageCount = jsonResponse.pageCount;
// Show empty row if no transaction found
if (self.pageCount == 0 || txlist.length == 0) {
self.emptyRow.classList.remove('hidden');
self.emptyRow.children[1].innerText = '{{ _("No transactions found...") }}';
self.pageCount = 1;
} else {
self.emptyRow.classList.add('hidden');
}
// If index is greater than the pages count fetch again for the last page
if (self.idx >= self.pageCount) {
self.idx = self.pageCount - 1;
fetchTxItems(self)
}
self.paginationIdxInput.value = self.idx + 1;
// If there are multiple pages show the pagination bar
if (self.pageCount > 1) {
self.paginationContainer.classList.remove('hidden');
self.paginationCounter.innerText = `of ${self.pageCount}`;
self.paginationIdxInput.max = self.pageCount;
} else {
self.paginationContainer.classList.add('hidden');
}
// Disable pagination back button if on the first page
if (self.idx == 0) {
self.paginationBack.classList.add('pagination-disabled');
} else {
self.paginationBack.classList.remove('pagination-disabled');
}
// Disable pagination next button if on the last page
if (self.idx + 1 == self.pageCount) {
self.paginationNext.classList.add('pagination-disabled');
} else {
self.paginationNext.classList.remove('pagination-disabled');
}
self.selectedRows = [];
self.selectedRowsActionBox.classList.add('hidden');
for (let txRow of self.shadowRoot.querySelectorAll('tx-row')) {
txRow.addEventListener('txRowSelected', e=>{
let selectedRow = e.target;
if (e.detail.selected) {
self.selectedRows.push({
txid: e.detail.txid,
vout: e.detail.vout,
amount: e.detail.amount.toFixed(8)
});
} else {
self.selectedRows = self.selectedRows.filter(row => !(row.txid === e.detail.txid && row.vout === e.detail.vout))
}
if (self.selectedRows.length == 0) {
self.selectedRowsActionBox.classList.add('hidden');
} else {
self.selectedRowsActionBox.classList.remove('hidden');
}
});
}
}
return
};
showError('{{ _("Failed to fetch transactions list.") }}')
} catch(e) {
console.log("Caught error: ", e);
showError(`{{ _("Failed to fetch transactions list.") }}: ${e}`);
}
}
/**
* Shows a <tx-data> element popup for specified txid.
* @param btcUnit - Bitcoin unit to display amounts with. Either "btc" or "sat"
* @param price - BTC price for price calculations
* @param symbol - Currency symbol for price calculations
* @param txid - ID of the transaction to show.
* @param wallet - The wallet alias of the wallet the transaction is associated with.
*/
function showTxData(btcUnit, price, symbol, txid, wallet) {
if (wallet) {
let txDataPopup = document.getElementById('tx-popup');
txDataPopup.innerHTML = `
<tx-data
data-btc-unit="${btcUnit ? btcUnit : 'btc'}"
data-price="${price ? price : 0}"
data-symbol="${symbol ? symbol : ''}"
data-txid="${txid}"
data-tx-wallet="${wallet}">
</tx-data>`;
showPageOverlay('tx-popup');
} else {
copyText(txid, `{{ _("Copied transaction") }}: ${ txid }`);
}
}
/**
* Triggered when a tx-row is marked as selected/ unselected .
*
*/
function txRowToggled(txRow) {
// if ()
}
customElements.define('tx-table', TxTableElement);
</script>