mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-14 22:38:10 +01:00
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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
216
basicswap/static/js/modules/balance-updates.js
Normal file
216
basicswap/static/js/modules/balance-updates.js
Normal 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;
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user