GUI: Dynamic balances (WS) + Better Notifications (Toasts) + various fixes. (#332)

* GUI: Dynamic balances (WS) + various fixes.

* BLACK + FLAKE8

* Clean-up.

* Fix refresh intervals + Fix pending balance.

* Fix amounts scientific notation (1e-8)

* Better Notifications (Toasts)

* Removed duplicated code + Balance skip if the chain is still syncing.

* Fix MWEB doesnt show as pending + Various fixes.

* Fix: USD values are off with part blind.

* Fix: Percentage change buttons on wallet page.

* Cleanup debug on wallet page.

* Use ZMQ for part balances.

* Fix ZMQ config.

* Fix PART price in chart.
This commit is contained in:
Gerlof van Ek
2025-07-22 23:45:45 +02:00
committed by GitHub
parent d6ef4f2edb
commit a5cc83157d
24 changed files with 3199 additions and 302 deletions

View File

@@ -14,6 +14,62 @@
z-index: 9999;
}
/* Toast Notification Animations */
.toast-slide-in {
animation: slideInRight 0.3s ease-out;
}
.toast-slide-out {
animation: slideOutRight 0.3s ease-in forwards;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
/* Toast Container Styles */
#ul_updates {
list-style: none;
padding: 0;
margin: 0;
max-width: 400px;
}
#ul_updates li {
margin-bottom: 0.5rem;
}
/* Toast Hover Effects */
#ul_updates .bg-white:hover {
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transform: translateY(-1px);
transition: all 0.2s ease-in-out;
}
.dark #ul_updates .dark\:bg-gray-800:hover {
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
transition: all 0.2s ease-in-out;
}
/* Table Styles */
.padded_row td {
padding-top: 1.5em;

View File

@@ -23,13 +23,7 @@ const AmmTablesManager = (function() {
}
function debugLog(message, data) {
// if (isDebugEnabled()) {
// if (data) {
// console.log(`[AmmTables] ${message}`, data);
// } else {
// console.log(`[AmmTables] ${message}`);
// }
// }
}
function initializeTabs() {
@@ -309,7 +303,10 @@ const AmmTablesManager = (function() {
`;
});
offersBody.innerHTML = tableHtml;
if (offersBody.innerHTML.trim() !== tableHtml.trim()) {
offersBody.innerHTML = tableHtml;
}
}
function renderBidsTable(stateData) {
@@ -441,7 +438,10 @@ const AmmTablesManager = (function() {
`;
});
bidsBody.innerHTML = tableHtml;
if (bidsBody.innerHTML.trim() !== tableHtml.trim()) {
bidsBody.innerHTML = tableHtml;
}
}
function formatDuration(seconds) {
@@ -724,6 +724,429 @@ const AmmTablesManager = (function() {
}
}
function shouldDropdownOptionsShowBalance(select) {
const isMakerDropdown = select.id.includes('coin-from');
const isTakerDropdown = select.id.includes('coin-to');
const addModal = document.getElementById('add-amm-modal');
const editModal = document.getElementById('edit-amm-modal');
const addModalVisible = addModal && !addModal.classList.contains('hidden');
const editModalVisible = editModal && !editModal.classList.contains('hidden');
let isBidModal = false;
if (addModalVisible) {
const dataType = addModal.getAttribute('data-amm-type');
if (dataType) {
isBidModal = dataType === 'bid';
} else {
const modalTitle = document.getElementById('add-modal-title');
isBidModal = modalTitle && modalTitle.textContent && modalTitle.textContent.includes('Bid');
}
} else if (editModalVisible) {
const dataType = editModal.getAttribute('data-amm-type');
if (dataType) {
isBidModal = dataType === 'bid';
} else {
const modalTitle = document.getElementById('edit-modal-title');
isBidModal = modalTitle && modalTitle.textContent && modalTitle.textContent.includes('Bid');
}
}
const result = isBidModal ? isTakerDropdown : isMakerDropdown;
console.log(`[DEBUG] shouldDropdownOptionsShowBalance: ${select.id}, isBidModal=${isBidModal}, isMaker=${isMakerDropdown}, isTaker=${isTakerDropdown}, result=${result}`);
return result;
}
function refreshDropdownOptions() {
const dropdownIds = ['add-amm-coin-from', 'add-amm-coin-to', 'edit-amm-coin-from', 'edit-amm-coin-to'];
dropdownIds.forEach(dropdownId => {
const select = document.getElementById(dropdownId);
if (!select || select.style.display !== 'none') return;
const wrapper = select.parentNode.querySelector('.relative');
if (!wrapper) return;
const dropdown = wrapper.querySelector('[role="listbox"]');
if (!dropdown) return;
const options = dropdown.querySelectorAll('[data-value]');
options.forEach(optionElement => {
const coinValue = optionElement.getAttribute('data-value');
const originalOption = Array.from(select.options).find(opt => opt.value === coinValue);
if (!originalOption) return;
const textContainer = optionElement.querySelector('div.flex.flex-col, div.flex.items-center');
if (!textContainer) return;
textContainer.innerHTML = '';
const shouldShowBalance = shouldDropdownOptionsShowBalance(select);
const fullText = originalOption.textContent.trim();
const balance = originalOption.getAttribute('data-balance') || '0.00000000';
console.log(`[DEBUG] refreshDropdownOptions: ${select.id}, option=${coinValue}, shouldShowBalance=${shouldShowBalance}, balance=${balance}`);
if (shouldShowBalance) {
textContainer.className = 'flex flex-col';
const coinName = fullText.includes(' - Balance: ') ? fullText.split(' - Balance: ')[0] : fullText;
const coinNameSpan = document.createElement('span');
coinNameSpan.textContent = coinName;
coinNameSpan.className = 'text-gray-900 dark:text-white';
const balanceSpan = document.createElement('span');
balanceSpan.textContent = `Balance: ${balance}`;
balanceSpan.className = 'text-gray-500 dark:text-gray-400 text-xs';
textContainer.appendChild(coinNameSpan);
textContainer.appendChild(balanceSpan);
} else {
textContainer.className = 'flex items-center';
const coinNameSpan = document.createElement('span');
const coinName = fullText.includes(' - Balance: ') ? fullText.split(' - Balance: ')[0] : fullText;
coinNameSpan.textContent = coinName;
coinNameSpan.className = 'text-gray-900 dark:text-white';
textContainer.appendChild(coinNameSpan);
}
});
});
}
function refreshDropdownBalances() {
const dropdownIds = ['add-amm-coin-from', 'add-amm-coin-to', 'edit-amm-coin-from', 'edit-amm-coin-to'];
dropdownIds.forEach(dropdownId => {
const select = document.getElementById(dropdownId);
if (!select || select.style.display !== 'none') return;
const wrapper = select.parentNode.querySelector('.relative');
if (!wrapper) return;
const dropdownItems = wrapper.querySelectorAll('[data-value]');
dropdownItems.forEach(item => {
const value = item.getAttribute('data-value');
const option = select.querySelector(`option[value="${value}"]`);
if (option) {
const balance = option.getAttribute('data-balance') || '0.00000000';
const pendingBalance = option.getAttribute('data-pending-balance') || '';
const balanceDiv = item.querySelector('.text-xs');
if (balanceDiv) {
balanceDiv.textContent = `Balance: ${balance}`;
let pendingDiv = item.querySelector('.text-green-500');
if (pendingBalance && parseFloat(pendingBalance) > 0) {
if (!pendingDiv) {
pendingDiv = document.createElement('div');
pendingDiv.className = 'text-green-500 text-xs';
balanceDiv.parentNode.appendChild(pendingDiv);
}
pendingDiv.textContent = `+${pendingBalance} pending`;
} else if (pendingDiv) {
pendingDiv.remove();
}
}
}
});
const selectedOption = select.options[select.selectedIndex];
if (selectedOption) {
const textContainer = wrapper.querySelector('button .flex-grow');
const balanceDiv = textContainer ? textContainer.querySelector('.text-xs') : null;
if (balanceDiv) {
const balance = selectedOption.getAttribute('data-balance') || '0.00000000';
const pendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
balanceDiv.textContent = `Balance: ${balance}`;
let pendingDiv = textContainer.querySelector('.text-green-500');
if (pendingBalance && parseFloat(pendingBalance) > 0) {
if (!pendingDiv) {
pendingDiv = document.createElement('div');
pendingDiv.className = 'text-green-500 text-xs';
textContainer.appendChild(pendingDiv);
}
pendingDiv.textContent = `+${pendingBalance} pending`;
} else if (pendingDiv) {
pendingDiv.remove();
}
}
}
});
}
function refreshOfferDropdownBalanceDisplay() {
refreshDropdownBalances();
}
function refreshBidDropdownBalanceDisplay() {
refreshDropdownBalances();
}
function refreshDropdownBalanceDisplay(modalType = null) {
if (modalType === 'offer') {
refreshOfferDropdownBalanceDisplay();
} else if (modalType === 'bid') {
refreshBidDropdownBalanceDisplay();
} else {
const addModal = document.getElementById('add-amm-modal');
const editModal = document.getElementById('edit-amm-modal');
const addModalVisible = addModal && !addModal.classList.contains('hidden');
const editModalVisible = editModal && !editModal.classList.contains('hidden');
let detectedType = null;
if (addModalVisible) {
detectedType = addModal.getAttribute('data-amm-type');
} else if (editModalVisible) {
detectedType = editModal.getAttribute('data-amm-type');
}
if (detectedType === 'offer') {
refreshOfferDropdownBalanceDisplay();
} else if (detectedType === 'bid') {
refreshBidDropdownBalanceDisplay();
}
}
}
function updateDropdownsForModalType(modalPrefix) {
const coinFromSelect = document.getElementById(`${modalPrefix}-amm-coin-from`);
const coinToSelect = document.getElementById(`${modalPrefix}-amm-coin-to`);
if (!coinFromSelect || !coinToSelect) return;
const balanceData = {};
Array.from(coinFromSelect.options).forEach(option => {
const balance = option.getAttribute('data-balance');
if (balance) {
balanceData[option.value] = balance;
}
});
Array.from(coinToSelect.options).forEach(option => {
const balance = option.getAttribute('data-balance');
if (balance) {
balanceData[option.value] = balance;
}
});
updateDropdownOptions(coinFromSelect, balanceData);
updateDropdownOptions(coinToSelect, balanceData);
}
function updateDropdownOptions(select, balanceData, pendingData = {}) {
Array.from(select.options).forEach(option => {
const coinName = option.value;
const balance = balanceData[coinName] || '0.00000000';
const pending = pendingData[coinName] || '0.0';
option.setAttribute('data-balance', balance);
option.setAttribute('data-pending-balance', pending);
option.textContent = coinName;
});
}
function createSimpleDropdown(select, showBalance = false) {
if (!select) return;
const existingWrapper = select.parentNode.querySelector('.relative');
if (existingWrapper) {
existingWrapper.remove();
select.style.display = '';
}
select.style.display = 'none';
const wrapper = document.createElement('div');
wrapper.className = 'relative';
const button = document.createElement('button');
button.type = 'button';
button.className = 'flex items-center justify-between w-full p-2.5 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:text-white';
button.style.minHeight = '60px';
const displayContent = document.createElement('div');
displayContent.className = 'flex items-center';
const icon = document.createElement('img');
icon.className = 'w-5 h-5 mr-2';
icon.alt = '';
const textContainer = document.createElement('div');
textContainer.className = 'flex-grow text-left';
const arrow = document.createElement('div');
arrow.innerHTML = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>`;
displayContent.appendChild(icon);
displayContent.appendChild(textContainer);
button.appendChild(displayContent);
button.appendChild(arrow);
const dropdown = document.createElement('div');
dropdown.className = 'absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg hidden dark:bg-gray-700 dark:border-gray-600 max-h-60 overflow-y-auto';
Array.from(select.options).forEach(option => {
const item = document.createElement('div');
item.className = 'flex items-center p-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer';
item.setAttribute('data-value', option.value);
const itemIcon = document.createElement('img');
itemIcon.className = 'w-5 h-5 mr-2';
itemIcon.src = `/static/images/coins/${getImageFilename(option.value)}`;
itemIcon.alt = '';
const itemText = document.createElement('div');
const coinName = option.textContent.trim();
const balance = option.getAttribute('data-balance') || '0.00000000';
const pendingBalance = option.getAttribute('data-pending-balance') || '';
if (showBalance) {
itemText.className = 'flex flex-col';
let html = `
<div class="text-gray-900 dark:text-white">${coinName}</div>
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${balance}</div>
`;
if (pendingBalance && parseFloat(pendingBalance) > 0) {
html += `<div class="text-green-500 text-xs">+${pendingBalance} pending</div>`;
}
itemText.innerHTML = html;
} else {
itemText.className = 'text-gray-900 dark:text-white';
itemText.textContent = coinName;
}
item.appendChild(itemIcon);
item.appendChild(itemText);
item.addEventListener('click', function() {
select.value = this.getAttribute('data-value');
const selectedOption = select.options[select.selectedIndex];
const selectedCoinName = selectedOption.textContent.trim();
const selectedBalance = selectedOption.getAttribute('data-balance') || '0.00000000';
const selectedPendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
icon.src = itemIcon.src;
if (showBalance) {
let html = `
<div class="text-gray-900 dark:text-white">${selectedCoinName}</div>
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${selectedBalance}</div>
`;
if (selectedPendingBalance && parseFloat(selectedPendingBalance) > 0) {
html += `<div class="text-green-500 text-xs">+${selectedPendingBalance} pending</div>`;
}
textContainer.innerHTML = html;
textContainer.className = 'flex-grow text-left flex flex-col justify-center';
} else {
textContainer.textContent = selectedCoinName;
textContainer.className = 'flex-grow text-left';
}
dropdown.classList.add('hidden');
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
});
dropdown.appendChild(item);
});
const selectedOption = select.options[select.selectedIndex];
if (selectedOption) {
const selectedCoinName = selectedOption.textContent.trim();
const selectedBalance = selectedOption.getAttribute('data-balance') || '0.00000000';
const selectedPendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
if (showBalance) {
let html = `
<div class="text-gray-900 dark:text-white">${selectedCoinName}</div>
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${selectedBalance}</div>
`;
if (selectedPendingBalance && parseFloat(selectedPendingBalance) > 0) {
html += `<div class="text-green-500 text-xs">+${selectedPendingBalance} pending</div>`;
}
textContainer.innerHTML = html;
textContainer.className = 'flex-grow text-left flex flex-col justify-center';
} else {
textContainer.textContent = selectedCoinName;
textContainer.className = 'flex-grow text-left';
}
}
button.addEventListener('click', function() {
dropdown.classList.toggle('hidden');
});
document.addEventListener('click', function(e) {
if (!wrapper.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
wrapper.appendChild(button);
wrapper.appendChild(dropdown);
select.parentNode.insertBefore(wrapper, select);
}
function setupButtonHandlers() {
const addOfferButton = document.getElementById('add-new-offer-btn');
if (addOfferButton) {
@@ -844,6 +1267,40 @@ const AmmTablesManager = (function() {
modalTitle.textContent = `Add New ${type.charAt(0).toUpperCase() + type.slice(1)}`;
}
const modal = document.getElementById('add-amm-modal');
if (modal) {
modal.classList.remove('hidden');
modal.setAttribute('data-amm-type', type);
}
setTimeout(() => {
updateDropdownsForModalType('add');
initializeCustomSelects(type);
refreshDropdownBalanceDisplay(type);
if (typeof fetchBalanceData === 'function') {
fetchBalanceData()
.then(balanceData => {
if (type === 'offer' && typeof updateOfferDropdownBalances === 'function') {
updateOfferDropdownBalances(balanceData);
} else if (type === 'bid' && typeof updateBidDropdownBalances === 'function') {
updateBidDropdownBalances(balanceData);
}
})
.catch(error => {
console.error('Error updating dropdown balances:', error);
});
}
}, 50);
document.getElementById('add-amm-type').value = type;
document.getElementById('add-amm-name').value = 'Unnamed Offer';
@@ -940,11 +1397,6 @@ const AmmTablesManager = (function() {
if (type === 'offer') {
setupBiddingControls('add');
}
const modal = document.getElementById('add-amm-modal');
if (modal) {
modal.classList.remove('hidden');
}
}
function closeAddModal() {
@@ -1269,6 +1721,40 @@ const AmmTablesManager = (function() {
modalTitle.textContent = `Edit ${type.charAt(0).toUpperCase() + type.slice(1)}`;
}
const modal = document.getElementById('edit-amm-modal');
if (modal) {
modal.classList.remove('hidden');
modal.setAttribute('data-amm-type', type);
}
setTimeout(() => {
updateDropdownsForModalType('edit');
initializeCustomSelects(type);
refreshDropdownBalanceDisplay(type);
if (typeof fetchBalanceData === 'function') {
fetchBalanceData()
.then(balanceData => {
if (type === 'offer' && typeof updateOfferDropdownBalances === 'function') {
updateOfferDropdownBalances(balanceData);
} else if (type === 'bid' && typeof updateBidDropdownBalances === 'function') {
updateBidDropdownBalances(balanceData);
}
})
.catch(error => {
console.error('Error updating dropdown balances:', error);
});
}
}, 50);
document.getElementById('edit-amm-type').value = type;
document.getElementById('edit-amm-id').value = id || '';
document.getElementById('edit-amm-original-name').value = name;
@@ -1282,8 +1768,12 @@ const AmmTablesManager = (function() {
coinFromSelect.value = item.coin_from || '';
coinToSelect.value = item.coin_to || '';
coinFromSelect.dispatchEvent(new Event('change', { bubbles: true }));
coinToSelect.dispatchEvent(new Event('change', { bubbles: true }));
if (coinFromSelect) {
coinFromSelect.dispatchEvent(new Event('change', { bubbles: true }));
}
if (coinToSelect) {
coinToSelect.dispatchEvent(new Event('change', { bubbles: true }));
}
document.getElementById('edit-amm-amount').value = item.amount || '';
@@ -1370,11 +1860,6 @@ const AmmTablesManager = (function() {
setupBiddingControls('edit');
populateBiddingControls('edit', item);
}
const modal = document.getElementById('edit-amm-modal');
if (modal) {
modal.classList.remove('hidden');
}
} catch (error) {
alert(`Error processing the configuration: ${error.message}`);
debugLog('Error opening edit modal:', error);
@@ -1808,7 +2293,7 @@ const AmmTablesManager = (function() {
}
}
function initializeCustomSelects() {
function initializeCustomSelects(modalType = null) {
const coinSelects = [
document.getElementById('add-amm-coin-from'),
document.getElementById('add-amm-coin-to'),
@@ -1821,116 +2306,16 @@ const AmmTablesManager = (function() {
document.getElementById('edit-offer-swap-type')
];
function createCoinDropdown(select) {
if (!select) return;
const wrapper = document.createElement('div');
wrapper.className = 'relative';
const display = document.createElement('div');
display.className = 'flex items-center w-full p-2.5 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white cursor-pointer';
const icon = document.createElement('img');
icon.className = 'w-5 h-5 mr-2';
icon.alt = '';
const text = document.createElement('span');
text.className = 'flex-grow';
const arrow = document.createElement('span');
arrow.className = 'ml-2';
arrow.innerHTML = `
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
`;
display.appendChild(icon);
display.appendChild(text);
display.appendChild(arrow);
const dropdown = document.createElement('div');
dropdown.className = 'absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg hidden dark:bg-gray-700 dark:border-gray-600 max-h-60 overflow-y-auto';
Array.from(select.options).forEach(option => {
const item = document.createElement('div');
item.className = 'flex items-center p-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-900 dark:text-white';
item.setAttribute('data-value', option.value);
item.setAttribute('data-symbol', option.getAttribute('data-symbol') || '');
const optionIcon = document.createElement('img');
optionIcon.className = 'w-5 h-5 mr-2';
optionIcon.src = `/static/images/coins/${getImageFilename(option.value)}`;
optionIcon.alt = '';
const optionText = document.createElement('span');
optionText.textContent = option.textContent.trim();
item.appendChild(optionIcon);
item.appendChild(optionText);
item.addEventListener('click', function() {
select.value = this.getAttribute('data-value');
text.textContent = optionText.textContent;
icon.src = optionIcon.src;
dropdown.classList.add('hidden');
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
if (select.id === 'add-amm-coin-from' || select.id === 'add-amm-coin-to') {
const coinFrom = document.getElementById('add-amm-coin-from');
const coinTo = document.getElementById('add-amm-coin-to');
const swapType = document.getElementById('add-offer-swap-type');
if (coinFrom && coinTo && swapType) {
updateSwapTypeOptions(coinFrom.value, coinTo.value, swapType);
}
} else if (select.id === 'edit-amm-coin-from' || select.id === 'edit-amm-coin-to') {
const coinFrom = document.getElementById('edit-amm-coin-from');
const coinTo = document.getElementById('edit-amm-coin-to');
const swapType = document.getElementById('edit-offer-swap-type');
if (coinFrom && coinTo && swapType) {
updateSwapTypeOptions(coinFrom.value, coinTo.value, swapType);
}
}
});
dropdown.appendChild(item);
});
const selectedOption = select.options[select.selectedIndex];
text.textContent = selectedOption.textContent.trim();
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
display.addEventListener('click', function(e) {
e.stopPropagation();
dropdown.classList.toggle('hidden');
});
document.addEventListener('click', function() {
dropdown.classList.add('hidden');
});
wrapper.appendChild(display);
wrapper.appendChild(dropdown);
select.parentNode.insertBefore(wrapper, select);
select.style.display = 'none';
select.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
text.textContent = selectedOption.textContent.trim();
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
});
}
function createSwapTypeDropdown(select) {
if (!select) return;
if (select.style.display === 'none' && select.parentNode.querySelector('.relative')) {
return; // Custom dropdown already exists
}
const wrapper = document.createElement('div');
wrapper.className = 'relative';
@@ -1980,7 +2365,9 @@ const AmmTablesManager = (function() {
});
const selectedOption = select.options[select.selectedIndex];
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
if (selectedOption) {
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
}
display.addEventListener('click', function(e) {
if (select.disabled) return;
@@ -2000,7 +2387,9 @@ const AmmTablesManager = (function() {
select.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
if (selectedOption) {
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
}
});
const observer = new MutationObserver(function(mutations) {
@@ -2022,7 +2411,18 @@ const AmmTablesManager = (function() {
}
}
coinSelects.forEach(select => createCoinDropdown(select));
coinSelects.forEach(select => {
if (!select) return;
let showBalance = false;
if (modalType === 'offer' && select.id.includes('coin-from')) {
showBalance = true; // OFFER: maker shows balance
} else if (modalType === 'bid' && select.id.includes('coin-to')) {
showBalance = true; // BID: taker shows balance
}
createSimpleDropdown(select, showBalance);
});
swapTypeSelects.forEach(select => createSwapTypeDropdown(select));
}
@@ -2301,19 +2701,27 @@ const AmmTablesManager = (function() {
if (refreshButton) {
refreshButton.addEventListener('click', async function() {
if (refreshButton.disabled) return;
const icon = refreshButton.querySelector('svg');
refreshButton.disabled = true;
if (icon) {
icon.classList.add('animate-spin');
}
await initializePrices();
updateTables();
setTimeout(() => {
if (icon) {
icon.classList.remove('animate-spin');
}
}, 1000);
try {
await initializePrices();
updateTables();
} finally {
setTimeout(() => {
if (icon) {
icon.classList.remove('animate-spin');
}
refreshButton.disabled = false;
}, 500); // Reduced from 1000ms to 500ms
}
});
}
@@ -2326,7 +2734,11 @@ const AmmTablesManager = (function() {
return {
updateTables,
startRefreshTimer,
stopRefreshTimer
stopRefreshTimer,
refreshDropdownBalanceDisplay,
refreshOfferDropdownBalanceDisplay,
refreshBidDropdownBalanceDisplay,
refreshDropdownOptions
};
}

View File

@@ -367,16 +367,45 @@ const ApiManager = (function() {
const results = {};
const fetchPromises = coinSymbols.map(async coin => {
if (coin === 'WOW') {
let useCoinGecko = false;
let coingeckoId = null;
if (window.CoinManager) {
const coinConfig = window.CoinManager.getCoinByAnyIdentifier(coin);
if (coinConfig) {
useCoinGecko = !coinConfig.usesCryptoCompare || coin === 'PART';
coingeckoId = coinConfig.coingeckoId;
}
} else {
const coinGeckoCoins = {
'WOW': 'wownero',
'PART': 'particl',
'BTC': 'bitcoin'
};
if (coinGeckoCoins[coin]) {
useCoinGecko = true;
coingeckoId = coinGeckoCoins[coin];
}
}
if (useCoinGecko && coingeckoId) {
return this.rateLimiter.queueRequest('coingecko', async () => {
const url = `https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=1`;
let days;
if (resolution === 'day') {
days = 1;
} else if (resolution === 'year') {
days = 365;
} else {
days = 180;
}
const url = `https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`;
try {
const response = await this.makePostRequest(url);
if (response && response.prices) {
results[coin] = response.prices;
}
} catch (error) {
console.error(`Error fetching CoinGecko data for WOW:`, error);
console.error(`Error fetching CoinGecko data for ${coin}:`, error);
throw error;
}
});

View File

@@ -0,0 +1,216 @@
const BalanceUpdatesManager = (function() {
'use strict';
const config = {
balanceUpdateDelay: 2000,
swapEventDelay: 5000,
periodicRefreshInterval: 120000,
walletPeriodicRefreshInterval: 60000,
};
const state = {
handlers: new Map(),
timeouts: new Map(),
intervals: new Map(),
initialized: false
};
function fetchBalanceData() {
return fetch('/json/walletbalances', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`Server error: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(balanceData => {
if (balanceData.error) {
throw new Error(balanceData.error);
}
if (!Array.isArray(balanceData)) {
throw new Error('Invalid response format');
}
return balanceData;
});
}
function clearTimeoutByKey(key) {
if (state.timeouts.has(key)) {
clearTimeout(state.timeouts.get(key));
state.timeouts.delete(key);
}
}
function setTimeoutByKey(key, callback, delay) {
clearTimeoutByKey(key);
const timeoutId = setTimeout(callback, delay);
state.timeouts.set(key, timeoutId);
}
function clearIntervalByKey(key) {
if (state.intervals.has(key)) {
clearInterval(state.intervals.get(key));
state.intervals.delete(key);
}
}
function setIntervalByKey(key, callback, interval) {
clearIntervalByKey(key);
const intervalId = setInterval(callback, interval);
state.intervals.set(key, intervalId);
}
function handleBalanceUpdate(contextKey, updateCallback, errorContext) {
clearTimeoutByKey(`${contextKey}_balance_update`);
setTimeoutByKey(`${contextKey}_balance_update`, () => {
fetchBalanceData()
.then(balanceData => {
updateCallback(balanceData);
})
.catch(error => {
console.error(`Error updating ${errorContext} balances via WebSocket:`, error);
});
}, config.balanceUpdateDelay);
}
function handleSwapEvent(contextKey, updateCallback, errorContext) {
clearTimeoutByKey(`${contextKey}_swap_event`);
setTimeoutByKey(`${contextKey}_swap_event`, () => {
fetchBalanceData()
.then(balanceData => {
updateCallback(balanceData);
})
.catch(error => {
console.error(`Error updating ${errorContext} balances via swap event:`, error);
});
}, config.swapEventDelay);
}
function setupWebSocketHandler(contextKey, balanceUpdateCallback, swapEventCallback, errorContext) {
const handlerId = window.WebSocketManager.addMessageHandler('message', (data) => {
if (data && data.event) {
if (data.event === 'coin_balance_updated') {
handleBalanceUpdate(contextKey, balanceUpdateCallback, errorContext);
}
if (swapEventCallback) {
const swapEvents = ['new_bid', 'bid_accepted', 'swap_completed'];
if (swapEvents.includes(data.event)) {
handleSwapEvent(contextKey, swapEventCallback, errorContext);
}
}
}
});
state.handlers.set(contextKey, handlerId);
return handlerId;
}
function setupPeriodicRefresh(contextKey, updateCallback, errorContext, interval) {
const refreshInterval = interval || config.periodicRefreshInterval;
setIntervalByKey(`${contextKey}_periodic`, () => {
fetchBalanceData()
.then(balanceData => {
updateCallback(balanceData);
})
.catch(error => {
console.error(`Error in periodic ${errorContext} balance refresh:`, error);
});
}, refreshInterval);
}
function cleanup(contextKey) {
if (state.handlers.has(contextKey)) {
const handlerId = state.handlers.get(contextKey);
if (window.WebSocketManager && typeof window.WebSocketManager.removeMessageHandler === 'function') {
window.WebSocketManager.removeMessageHandler('message', handlerId);
}
state.handlers.delete(contextKey);
}
clearTimeoutByKey(`${contextKey}_balance_update`);
clearTimeoutByKey(`${contextKey}_swap_event`);
clearIntervalByKey(`${contextKey}_periodic`);
}
function cleanupAll() {
state.handlers.forEach((handlerId) => {
if (window.WebSocketManager && typeof window.WebSocketManager.removeMessageHandler === 'function') {
window.WebSocketManager.removeMessageHandler('message', handlerId);
}
});
state.handlers.clear();
state.timeouts.forEach(timeoutId => clearTimeout(timeoutId));
state.timeouts.clear();
state.intervals.forEach(intervalId => clearInterval(intervalId));
state.intervals.clear();
state.initialized = false;
}
return {
initialize: function() {
if (state.initialized) {
return this;
}
if (window.CleanupManager) {
window.CleanupManager.registerResource('balanceUpdatesManager', this, (mgr) => mgr.dispose());
}
window.addEventListener('beforeunload', cleanupAll);
state.initialized = true;
console.log('BalanceUpdatesManager initialized');
return this;
},
setup: function(options) {
const {
contextKey,
balanceUpdateCallback,
swapEventCallback,
errorContext,
enablePeriodicRefresh = false,
periodicInterval
} = options;
if (!contextKey || !balanceUpdateCallback || !errorContext) {
throw new Error('Missing required options: contextKey, balanceUpdateCallback, errorContext');
}
setupWebSocketHandler(contextKey, balanceUpdateCallback, swapEventCallback, errorContext);
if (enablePeriodicRefresh) {
setupPeriodicRefresh(contextKey, balanceUpdateCallback, errorContext, periodicInterval);
}
return this;
},
fetchBalanceData: fetchBalanceData,
cleanup: cleanup,
dispose: cleanupAll,
isInitialized: function() {
return state.initialized;
}
};
})();
if (typeof window !== 'undefined') {
window.BalanceUpdatesManager = BalanceUpdatesManager;
}

View File

@@ -1,11 +1,40 @@
const NotificationManager = (function() {
const config = {
const defaultConfig = {
showNewOffers: false,
showNewBids: true,
showBidAccepted: true
showBidAccepted: true,
showBalanceChanges: true,
showOutgoingTransactions: true,
notificationDuration: 20000
};
function loadConfig() {
const saved = localStorage.getItem('notification_settings');
if (saved) {
try {
return { ...defaultConfig, ...JSON.parse(saved) };
} catch (e) {
console.error('Error loading notification settings:', e);
}
}
return { ...defaultConfig };
}
function saveConfig(newConfig) {
try {
localStorage.setItem('notification_settings', JSON.stringify(newConfig));
Object.assign(config, newConfig);
} catch (e) {
console.error('Error saving notification settings:', e);
}
}
let config = loadConfig();
function ensureToastContainer() {
let container = document.getElementById('ul_updates');
if (!container) {
@@ -19,13 +48,67 @@ const NotificationManager = (function() {
return container;
}
function getCoinIcon(coinSymbol) {
if (window.CoinManager && typeof window.CoinManager.getCoinIcon === 'function') {
return window.CoinManager.getCoinIcon(coinSymbol);
}
return 'default.png';
}
function getToastIcon(type) {
const icons = {
'new_offer': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>`,
'new_bid': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
</svg>`,
'bid_accepted': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>`,
'balance_change': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
</svg>`,
'success': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>`
};
return icons[type] || icons['success'];
}
function getToastColor(type, options = {}) {
const colors = {
'new_offer': 'bg-blue-500',
'new_bid': 'bg-green-500',
'bid_accepted': 'bg-purple-500',
'balance_change': 'bg-yellow-500',
'success': 'bg-blue-500'
};
if (type === 'balance_change' && options.subtitle) {
if (options.subtitle.includes('sent') || options.subtitle.includes('sending')) {
return 'bg-red-500';
} else {
return 'bg-green-500';
}
}
return colors[type] || colors['success'];
}
const publicAPI = {
initialize: function(options = {}) {
Object.assign(config, options);
this.initializeBalanceTracking();
if (window.CleanupManager) {
window.CleanupManager.registerResource('notificationManager', this, (mgr) => {
if (this.balanceTimeouts) {
Object.values(this.balanceTimeouts).forEach(timeout => clearTimeout(timeout));
}
console.log('NotificationManager disposed');
});
}
@@ -33,33 +116,160 @@ const NotificationManager = (function() {
return this;
},
createToast: function(title, type = 'success') {
updateSettings: function(newSettings) {
saveConfig(newSettings);
return this;
},
getSettings: function() {
return { ...config };
},
testToasts: function() {
if (!this.createToast) return;
setTimeout(() => {
this.createToast(
'+0.05000000 PART',
'balance_change',
{ coinSymbol: 'PART', subtitle: 'Incoming funds pending' }
);
}, 500);
setTimeout(() => {
this.createToast(
'+0.00123456 XMR',
'balance_change',
{ coinSymbol: 'XMR', subtitle: 'Incoming funds confirmed' }
);
}, 1000);
setTimeout(() => {
this.createToast(
'-29.86277595 PART',
'balance_change',
{ coinSymbol: 'PART', subtitle: 'Funds sent' }
);
}, 1500);
setTimeout(() => {
this.createToast(
'-0.05000000 PART (Anon)',
'balance_change',
{ coinSymbol: 'PART', subtitle: 'Funds sending' }
);
}, 2000);
setTimeout(() => {
this.createToast(
'+1.23456789 PART (Anon)',
'balance_change',
{ coinSymbol: 'PART', subtitle: 'Incoming funds confirmed' }
);
}, 2500);
setTimeout(() => {
this.createToast(
'New network offer',
'new_offer',
{ offerId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66', subtitle: 'Click to view offer' }
);
}, 3000);
setTimeout(() => {
this.createToast(
'New bid received',
'new_bid',
{ bidId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66', subtitle: 'Click to view bid' }
);
}, 3500);
},
initializeBalanceTracking: function() {
fetch('/json/walletbalances')
.then(response => response.json())
.then(balanceData => {
if (Array.isArray(balanceData)) {
balanceData.forEach(coin => {
const balance = parseFloat(coin.balance) || 0;
const pending = parseFloat(coin.pending) || 0;
const coinKey = coin.name.replace(/\s+/g, '_');
const storageKey = `prev_balance_${coinKey}`;
const pendingStorageKey = `prev_pending_${coinKey}`;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, balance.toString());
}
if (!localStorage.getItem(pendingStorageKey)) {
localStorage.setItem(pendingStorageKey, pending.toString());
}
});
}
})
.catch(error => {
console.error('Error initializing balance tracking:', error);
});
},
createToast: function(title, type = 'success', options = {}) {
const messages = ensureToastContainer();
const message = document.createElement('li');
const toastId = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const iconColor = getToastColor(type, options);
const icon = getToastIcon(type);
let coinIconHtml = '';
if (options.coinSymbol) {
const coinIcon = getCoinIcon(options.coinSymbol);
coinIconHtml = `<img src="/static/images/coins/${coinIcon}" class="w-5 h-5 mr-2" alt="${options.coinSymbol}" onerror="this.style.display='none'">`;
}
let clickAction = '';
let cursorStyle = 'cursor-default';
if (options.offerId) {
clickAction = `onclick="window.location.href='/offer/${options.offerId}'"`;
cursorStyle = 'cursor-pointer';
} else if (options.bidId) {
clickAction = `onclick="window.location.href='/bid/${options.bidId}'"`;
cursorStyle = 'cursor-pointer';
} else if (options.coinSymbol) {
clickAction = `onclick="window.location.href='/wallet/${options.coinSymbol}'"`;
cursorStyle = 'cursor-pointer';
}
message.innerHTML = `
<div id="hide">
<div id="toast-${type}" class="flex items-center p-4 mb-4 w-full max-w-xs text-gray-500
bg-white rounded-lg shadow" role="alert">
<div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10
bg-blue-500 rounded-lg">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="18" width="18"
viewBox="0 0 24 24">
<g fill="#ffffff">
<path d="M8.5,20a1.5,1.5,0,0,1-1.061-.439L.379,12.5,2.5,10.379l6,6,13-13L23.621,
5.5,9.561,19.561A1.5,1.5,0,0,1,8.5,20Z"></path>
</g>
</svg>
<div class="toast-slide-in">
<div id="${toastId}" class="flex items-center p-4 mb-3 w-full max-w-sm text-gray-500
bg-white dark:bg-gray-800 dark:text-gray-400 rounded-lg shadow-lg border border-gray-200
dark:border-gray-700 ${cursorStyle} hover:shadow-xl transition-shadow" role="alert" ${clickAction}>
<div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10
${iconColor} rounded-lg text-white">
${icon}
</div>
<div class="uppercase w-40 ml-3 text-sm font-semibold text-gray-900">${title}</div>
<button type="button" onclick="closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5
bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-0 focus:outline-none
focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8">
<div class="flex items-center ml-3 text-sm font-medium text-gray-900 dark:text-white">
${coinIconHtml}
<div class="flex flex-col">
<span class="font-semibold">${title}</span>
${options.subtitle ? `<span class="text-xs text-gray-500 dark:text-gray-400">${options.subtitle}</span>` : ''}
</div>
</div>
<button type="button" onclick="event.stopPropagation(); closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5
bg-white dark:bg-gray-800 text-gray-400 hover:text-gray-900 dark:hover:text-white
rounded-lg p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 inline-flex h-8 w-8 transition-colors
focus:outline-none">
<span class="sr-only">Close</span>
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1
1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293
4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1
1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293
4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</button>
@@ -67,34 +277,206 @@ const NotificationManager = (function() {
</div>
`;
messages.appendChild(message);
setTimeout(() => {
if (message.parentNode) {
message.classList.add('toast-slide-out');
setTimeout(() => {
if (message.parentNode) {
message.parentNode.removeChild(message);
}
}, 300);
}
}, config.notificationDuration);
},
handleWebSocketEvent: function(data) {
if (!data || !data.event) return;
let toastTitle;
let toastTitle, toastType, toastOptions = {};
let shouldShowToast = false;
switch (data.event) {
case 'new_offer':
toastTitle = `New network <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
toastTitle = `New network offer`;
toastType = 'new_offer';
toastOptions.offerId = data.offer_id;
toastOptions.subtitle = 'Click to view offer';
shouldShowToast = config.showNewOffers;
break;
case 'new_bid':
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>New bid</a> on
<a class="underline" href=/offer/${data.offer_id}>offer</a>`;
toastTitle = `New bid received`;
toastOptions.bidId = data.bid_id;
toastOptions.subtitle = 'Click to view bid';
toastType = 'new_bid';
shouldShowToast = config.showNewBids;
break;
case 'bid_accepted':
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>Bid</a> accepted`;
toastTitle = `Bid accepted`;
toastOptions.bidId = data.bid_id;
toastOptions.subtitle = 'Click to view swap';
toastType = 'bid_accepted';
shouldShowToast = config.showBidAccepted;
break;
case 'coin_balance_updated':
if (data.coin && config.showBalanceChanges) {
this.handleBalanceUpdate(data);
}
return;
}
if (toastTitle && shouldShowToast) {
this.createToast(toastTitle);
this.createToast(toastTitle, toastType, toastOptions);
}
},
handleBalanceUpdate: function(data) {
if (!data.coin) return;
this.fetchAndShowBalanceChange(data.coin);
const balanceKey = `balance_${data.coin}`;
if (this.balanceTimeouts && this.balanceTimeouts[balanceKey]) {
clearTimeout(this.balanceTimeouts[balanceKey]);
}
if (!this.balanceTimeouts) {
this.balanceTimeouts = {};
}
this.balanceTimeouts[balanceKey] = setTimeout(() => {
this.fetchAndShowBalanceChange(data.coin);
}, 2000);
},
fetchAndShowBalanceChange: function(coinSymbol) {
fetch('/json/walletbalances')
.then(response => response.json())
.then(balanceData => {
if (Array.isArray(balanceData)) {
let coinsToCheck;
if (coinSymbol === 'PART') {
coinsToCheck = balanceData.filter(coin => coin.ticker === 'PART');
} else if (coinSymbol === 'LTC') {
coinsToCheck = balanceData.filter(coin => coin.ticker === 'LTC');
} else {
coinsToCheck = balanceData.filter(coin =>
coin.ticker === coinSymbol ||
coin.name.toLowerCase() === coinSymbol.toLowerCase()
);
}
coinsToCheck.forEach(coinData => {
this.checkSingleCoinBalance(coinData, coinSymbol);
});
}
})
.catch(error => {
console.error('Error fetching balance for notification:', error);
});
},
checkSingleCoinBalance: function(coinData, originalCoinSymbol) {
const currentBalance = parseFloat(coinData.balance) || 0;
const currentPending = parseFloat(coinData.pending) || 0;
const coinKey = coinData.name.replace(/\s+/g, '_');
const storageKey = `prev_balance_${coinKey}`;
const pendingStorageKey = `prev_pending_${coinKey}`;
const prevBalance = parseFloat(localStorage.getItem(storageKey)) || 0;
const prevPending = parseFloat(localStorage.getItem(pendingStorageKey)) || 0;
const balanceIncrease = currentBalance - prevBalance;
const pendingIncrease = currentPending - prevPending;
const pendingDecrease = prevPending - currentPending;
const isPendingToConfirmed = pendingDecrease > 0.00000001 && balanceIncrease > 0.00000001;
const displaySymbol = originalCoinSymbol;
let variantInfo = '';
if (coinData.name !== 'Particl' && coinData.name.includes('Particl')) {
variantInfo = ` (${coinData.name.replace('Particl ', '')})`;
} else if (coinData.name !== 'Litecoin' && coinData.name.includes('Litecoin')) {
variantInfo = ` (${coinData.name.replace('Litecoin ', '')})`;
}
if (balanceIncrease > 0.00000001) {
const displayAmount = balanceIncrease.toFixed(8).replace(/\.?0+$/, '');
const subtitle = isPendingToConfirmed ? 'Funds confirmed' : 'Incoming funds confirmed';
this.createToast(
`+${displayAmount} ${displaySymbol}${variantInfo}`,
'balance_change',
{
coinSymbol: originalCoinSymbol,
subtitle: subtitle
}
);
}
if (balanceIncrease < -0.00000001 && config.showOutgoingTransactions) {
const displayAmount = Math.abs(balanceIncrease).toFixed(8).replace(/\.?0+$/, '');
this.createToast(
`-${displayAmount} ${displaySymbol}${variantInfo}`,
'balance_change',
{
coinSymbol: originalCoinSymbol,
subtitle: 'Funds sent'
}
);
}
if (pendingIncrease > 0.00000001) {
const displayAmount = pendingIncrease.toFixed(8).replace(/\.?0+$/, '');
this.createToast(
`+${displayAmount} ${displaySymbol}${variantInfo}`,
'balance_change',
{
coinSymbol: originalCoinSymbol,
subtitle: 'Incoming funds pending'
}
);
}
if (pendingIncrease < -0.00000001 && config.showOutgoingTransactions && !isPendingToConfirmed) {
const displayAmount = Math.abs(pendingIncrease).toFixed(8).replace(/\.?0+$/, '');
this.createToast(
`-${displayAmount} ${displaySymbol}${variantInfo}`,
'balance_change',
{
coinSymbol: originalCoinSymbol,
subtitle: 'Funds sending'
}
);
}
if (pendingDecrease > 0.00000001 && !isPendingToConfirmed) {
const displayAmount = pendingDecrease.toFixed(8).replace(/\.?0+$/, '');
this.createToast(
`${displayAmount} ${displaySymbol}${variantInfo}`,
'balance_change',
{
coinSymbol: originalCoinSymbol,
subtitle: 'Pending funds confirmed'
}
);
}
localStorage.setItem(storageKey, currentBalance.toString());
localStorage.setItem(pendingStorageKey, currentPending.toString());
},
updateConfig: function(newConfig) {
Object.assign(config, newConfig);
return this;
@@ -122,5 +504,5 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
//console.log('NotificationManager initialized with methods:', Object.keys(NotificationManager));
console.log('NotificationManager initialized');

View File

@@ -44,6 +44,7 @@ const PriceManager = (function() {
setTimeout(() => this.getPrices(), 1500);
isInitialized = true;
console.log('PriceManager initialized');
return this;
},
@@ -59,7 +60,7 @@ const PriceManager = (function() {
return fetchPromise;
}
//console.log('PriceManager: Fetching latest prices.');
lastFetchTime = Date.now();
fetchPromise = this.fetchPrices()
.then(prices => {
@@ -166,14 +167,14 @@ const PriceManager = (function() {
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
if (cachedData) {
console.log('Using cached price data');
return cachedData.value;
}
try {
const existingCache = localStorage.getItem(PRICES_CACHE_KEY);
if (existingCache) {
console.log('Using localStorage cached price data');
return JSON.parse(existingCache).value;
}
} catch (e) {
@@ -230,4 +231,4 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
console.log('PriceManager initialized');

View File

@@ -261,9 +261,12 @@ const SummaryManager = (function() {
}
if (data.event) {
publicAPI.fetchSummaryData()
.then(() => {})
.catch(() => {});
const summaryEvents = ['new_offer', 'offer_revoked', 'new_bid', 'bid_accepted', 'swap_completed'];
if (summaryEvents.includes(data.event)) {
publicAPI.fetchSummaryData()
.then(() => {})
.catch(() => {});
}
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
window.NotificationManager.handleWebSocketEvent(data);
@@ -334,9 +337,12 @@ const SummaryManager = (function() {
wsManager.addMessageHandler('message', (data) => {
if (data.event) {
this.fetchSummaryData()
.then(() => {})
.catch(() => {});
const summaryEvents = ['new_offer', 'offer_revoked', 'new_bid', 'bid_accepted', 'swap_completed'];
if (summaryEvents.includes(data.event)) {
this.fetchSummaryData()
.then(() => {})
.catch(() => {});
}
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
window.NotificationManager.handleWebSocketEvent(data);

View File

@@ -180,8 +180,29 @@ const WalletManager = (function() {
if (coinSymbol) {
if (coinName === 'Particl') {
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
let isBlind = false;
let isAnon = false;
const flexContainer = el.closest('.flex');
if (flexContainer) {
const h4Element = flexContainer.querySelector('h4');
if (h4Element) {
isBlind = h4Element.textContent?.includes('Blind');
isAnon = h4Element.textContent?.includes('Anon');
}
}
if (!isBlind && !isAnon) {
const parentRow = el.closest('tr');
if (parentRow) {
const labelCell = parentRow.querySelector('td:first-child');
if (labelCell) {
isBlind = labelCell.textContent?.includes('Blind');
isAnon = labelCell.textContent?.includes('Anon');
}
}
}
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
} else if (coinName === 'Litecoin') {
@@ -248,8 +269,29 @@ const WalletManager = (function() {
const usdValue = (amount * price).toFixed(2);
if (coinName === 'Particl') {
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
let isBlind = false;
let isAnon = false;
const flexContainer = el.closest('.flex');
if (flexContainer) {
const h4Element = flexContainer.querySelector('h4');
if (h4Element) {
isBlind = h4Element.textContent?.includes('Blind');
isAnon = h4Element.textContent?.includes('Anon');
}
}
if (!isBlind && !isAnon) {
const parentRow = el.closest('tr');
if (parentRow) {
const labelCell = parentRow.querySelector('td:first-child');
if (labelCell) {
isBlind = labelCell.textContent?.includes('Blind');
isAnon = labelCell.textContent?.includes('Anon');
}
}
}
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
localStorage.setItem(`particl-${balanceType}-last-value`, usdValue);
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());

View File

@@ -443,7 +443,45 @@ const UIEnhancer = {
const selectNameElement = select.nextElementSibling?.querySelector('.select-name');
if (selectNameElement) {
selectNameElement.textContent = name;
if (select.id === 'coin_from' && name.includes(' - Balance: ')) {
const parts = name.split(' - Balance: ');
const coinName = parts[0];
const balanceInfo = parts[1] || '';
selectNameElement.innerHTML = '';
selectNameElement.style.display = 'flex';
selectNameElement.style.flexDirection = 'column';
selectNameElement.style.alignItems = 'flex-start';
selectNameElement.style.lineHeight = '1.2';
const coinNameDiv = document.createElement('div');
coinNameDiv.textContent = coinName;
coinNameDiv.style.fontWeight = 'normal';
coinNameDiv.style.color = 'inherit';
const balanceDiv = document.createElement('div');
balanceDiv.textContent = `Balance: ${balanceInfo}`;
balanceDiv.style.fontSize = '0.75rem';
balanceDiv.style.color = '#6b7280';
balanceDiv.style.marginTop = '1px';
selectNameElement.appendChild(coinNameDiv);
selectNameElement.appendChild(balanceDiv);
} else {
selectNameElement.textContent = name;
selectNameElement.style.display = 'block';
selectNameElement.style.flexDirection = '';
selectNameElement.style.alignItems = '';
}
}
updateSelectCache(select);

View File

@@ -43,7 +43,7 @@
});
window.bidsTabNavigationInitialized = true;
console.log('Bids tab navigation initialized');
//console.log('Bids tab navigation initialized');
}
function handleInitialNavigation() {
@@ -54,14 +54,14 @@
const tabToActivate = localStorage.getItem('bidsTabToActivate');
if (tabToActivate) {
//console.log('Activating tab from localStorage:', tabToActivate);
localStorage.removeItem('bidsTabToActivate');
activateTabWithRetry('#' + tabToActivate);
} else if (window.location.hash) {
//console.log('Activating tab from hash:', window.location.hash);
activateTabWithRetry(window.location.hash);
} else {
//console.log('Activating default tab: #all');
activateTabWithRetry('#all');
}
}
@@ -73,10 +73,10 @@
const hash = window.location.hash;
if (hash) {
//console.log('Hash changed, activating tab:', hash);
activateTabWithRetry(hash);
} else {
//console.log('Hash cleared, activating default tab: #all');
activateTabWithRetry('#all');
}
}
@@ -85,7 +85,7 @@
const normalizedTabId = tabId.startsWith('#') ? tabId : '#' + tabId;
if (normalizedTabId !== '#all' && normalizedTabId !== '#sent' && normalizedTabId !== '#received') {
//console.log('Invalid tab ID, defaulting to #all');
activateTabWithRetry('#all');
return;
}
@@ -96,17 +96,17 @@
if (!tabButton) {
if (retryCount < 5) {
//console.log('Tab button not found, retrying...', retryCount + 1);
setTimeout(() => {
activateTabWithRetry(normalizedTabId, retryCount + 1);
}, 100);
} else {
//console.error('Failed to find tab button after retries');
}
return;
}
//console.log('Activating tab:', normalizedTabId);
tabButton.click();
@@ -168,7 +168,7 @@
(tabId === '#sent' ? 'sent' : 'received');
if (typeof window.updateBidsTable === 'function') {
//console.log('Triggering data load for', tabId);
window.updateBidsTable();
}
}