mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-14 22:38:10 +01:00
GUI: Multi-select coin filtering / Various fixes. (#327)
* GUI: Multi-select coin filtering / Various fixes. * Use coin-manager / clean-up. * Fix BCH in filters + fix UX with bid pages modals when amount is empty. * Fix amount not empty. * Abandon Bid under debug_ui
This commit is contained in:
@@ -365,3 +365,147 @@ select.disabled-select-enabled {
|
||||
#toggle-auto-refresh[data-enabled="true"] {
|
||||
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||
}
|
||||
|
||||
/* Multi-select dropdown styles */
|
||||
.multi-select-dropdown::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.multi-select-dropdown::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.dark .multi-select-dropdown::-webkit-scrollbar-track {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.dark .multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||
background: #6b7280;
|
||||
}
|
||||
|
||||
.dark .multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.multi-select-dropdown input[type="checkbox"]:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-color: inherit !important;
|
||||
}
|
||||
|
||||
.multi-select-dropdown label:focus-within {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#coin_to_button:focus,
|
||||
#coin_from_button:focus {
|
||||
outline: none !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||
border-color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
.coin-badge {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 2px;
|
||||
}
|
||||
.coin-badge .remove {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
opacity: 0.7;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.coin-badge .remove:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.multi-select-dropdown {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
z-index: 9999 !important;
|
||||
position: fixed !important;
|
||||
min-width: 200px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.multi-select-dropdown::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.multi-select-dropdown::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
.dark .multi-select-dropdown::-webkit-scrollbar-track {
|
||||
background: #374151;
|
||||
}
|
||||
.dark .multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||
background: #6b7280;
|
||||
}
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.dropdown-container.open {
|
||||
z-index: 9999;
|
||||
}
|
||||
.filter-button-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.multi-select-dropdown input[type="checkbox"] {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.multi-select-dropdown input[type="checkbox"]:focus {
|
||||
outline: none !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||
border-color: #3b82f6 !important;
|
||||
}
|
||||
.multi-select-dropdown input[type="checkbox"]:checked {
|
||||
background-color: #3b82f6 !important;
|
||||
border-color: #3b82f6 !important;
|
||||
}
|
||||
.dark .multi-select-dropdown input[type="checkbox"] {
|
||||
border-color: #6b7280;
|
||||
background-color: #374151;
|
||||
}
|
||||
.dark .multi-select-dropdown input[type="checkbox"]:focus {
|
||||
border-color: #3b82f6 !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||
}
|
||||
.dark .multi-select-dropdown input[type="checkbox"]:checked {
|
||||
background-color: #3b82f6 !important;
|
||||
border-color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
.multi-select-dropdown label {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -61,64 +61,9 @@ const AmmTablesManager = (function() {
|
||||
function getImageFilename(coinSymbol) {
|
||||
if (!coinSymbol) return 'Unknown.png';
|
||||
|
||||
const coinNameToSymbol = {
|
||||
'bitcoin': 'BTC',
|
||||
'monero': 'XMR',
|
||||
'particl': 'PART',
|
||||
'particl anon': 'PART_ANON',
|
||||
'particl blind': 'PART_BLIND',
|
||||
'litecoin': 'LTC',
|
||||
'bitcoincash': 'BCH',
|
||||
'bitcoin cash': 'BCH',
|
||||
'firo': 'FIRO',
|
||||
'zcoin': 'FIRO',
|
||||
'pivx': 'PIVX',
|
||||
'dash': 'DASH',
|
||||
'ethereum': 'ETH',
|
||||
'dogecoin': 'DOGE',
|
||||
'decred': 'DCR',
|
||||
'namecoin': 'NMC',
|
||||
'zano': 'ZANO',
|
||||
'wownero': 'WOW'
|
||||
};
|
||||
|
||||
let normalizedInput = coinSymbol.toLowerCase();
|
||||
|
||||
if (coinNameToSymbol[normalizedInput]) {
|
||||
normalizedInput = coinNameToSymbol[normalizedInput];
|
||||
}
|
||||
|
||||
const normalizedSymbol = normalizedInput.toUpperCase();
|
||||
|
||||
if (normalizedSymbol === 'FIRO' || normalizedSymbol === 'ZCOIN') return 'Firo.png';
|
||||
if (normalizedSymbol === 'BCH' || normalizedSymbol === 'BITCOINCASH') return 'Bitcoin-Cash.png';
|
||||
if (normalizedSymbol === 'PART_ANON' || normalizedSymbol === 'PARTICL_ANON') return 'Particl.png';
|
||||
if (normalizedSymbol === 'PART_BLIND' || normalizedSymbol === 'PARTICL_BLIND') return 'Particl.png';
|
||||
|
||||
if (window.CoinManager && window.CoinManager.getCoinBySymbol) {
|
||||
const coin = window.CoinManager.getCoinBySymbol(normalizedSymbol);
|
||||
if (coin && coin.image) return coin.image;
|
||||
}
|
||||
|
||||
const coinImages = {
|
||||
'BTC': 'Bitcoin.png',
|
||||
'XMR': 'Monero.png',
|
||||
'PART': 'Particl.png',
|
||||
'LTC': 'Litecoin.png',
|
||||
'FIRO': 'Firo.png',
|
||||
'PIVX': 'PIVX.png',
|
||||
'DASH': 'Dash.png',
|
||||
'ETH': 'Ethereum.png',
|
||||
'DOGE': 'Dogecoin.png',
|
||||
'DCR': 'Decred.png',
|
||||
'NMC': 'Namecoin.png',
|
||||
'ZANO': 'Zano.png',
|
||||
'WOW': 'Wownero.png'
|
||||
};
|
||||
|
||||
const result = coinImages[normalizedSymbol] || 'Unknown.png';
|
||||
debugLog(`Coin symbol: ${coinSymbol}, normalized: ${normalizedSymbol}, image: ${result}`);
|
||||
return result;
|
||||
const icon = window.CoinManager.getCoinIcon(coinSymbol);
|
||||
debugLog(`CoinManager returned icon: ${icon} for ${coinSymbol}`);
|
||||
return icon || 'Unknown.png';
|
||||
}
|
||||
|
||||
function getCoinDisplayName(coinId) {
|
||||
|
||||
@@ -352,7 +352,7 @@ const createBidTableRow = async (bid) => {
|
||||
<div class="flex items-center justify-center">
|
||||
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||
<img class="h-12"
|
||||
src="/static/images/coins/${bid.coin_from.replace(' ', '-')}.png"
|
||||
src="/static/images/coins/${window.CoinManager.getCoinIcon(bid.coin_from)}"
|
||||
alt="${bid.coin_from}"
|
||||
onerror="this.src='/static/images/coins/default.png'">
|
||||
</span>
|
||||
@@ -361,7 +361,7 @@ const createBidTableRow = async (bid) => {
|
||||
</svg>
|
||||
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||
<img class="h-12"
|
||||
src="/static/images/coins/${bid.coin_to.replace(' ', '-')}.png"
|
||||
src="/static/images/coins/${window.CoinManager.getCoinIcon(bid.coin_to)}"
|
||||
alt="${bid.coin_to}"
|
||||
onerror="this.src='/static/images/coins/default.png'">
|
||||
</span>
|
||||
|
||||
@@ -509,6 +509,21 @@ function coinMatches(offerCoin, filterCoin) {
|
||||
return offerCoin === filterCoin;
|
||||
}
|
||||
|
||||
if (filterCoin.includes(' ') || offerCoin.includes(' ')) {
|
||||
const filterFirstWord = filterCoin.split(' ')[0];
|
||||
const offerFirstWord = offerCoin.split(' ')[0];
|
||||
|
||||
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||
const filterHasCash = filterCoin.includes('cash');
|
||||
const offerHasCash = offerCoin.includes('cash');
|
||||
return filterHasCash === offerHasCash;
|
||||
}
|
||||
|
||||
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const CoinManager = (function() {
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Bitcoin-Cash.png'
|
||||
icon: 'Bitcoin%20Cash.png'
|
||||
},
|
||||
{
|
||||
symbol: 'PIVX',
|
||||
@@ -235,6 +235,31 @@ const CoinManager = (function() {
|
||||
const coin = getCoinByAnyIdentifier(coinIdentifier);
|
||||
if (!coin) return coinIdentifier.toLowerCase();
|
||||
return coin.coingeckoId;
|
||||
},
|
||||
getCoinIcon: function(identifier) {
|
||||
if (!identifier) return null;
|
||||
|
||||
const normalizedId = identifier.toString().toLowerCase().trim();
|
||||
if (normalizedId === 'particl anon' || normalizedId === 'part_anon' || normalizedId === 'particl_anon') {
|
||||
return 'Particl.png';
|
||||
}
|
||||
if (normalizedId === 'particl blind' || normalizedId === 'part_blind' || normalizedId === 'particl_blind') {
|
||||
return 'Particl.png';
|
||||
}
|
||||
if (normalizedId === 'litecoin mweb' || normalizedId === 'ltc_mweb' || normalizedId === 'litecoin_mweb') {
|
||||
return 'Litecoin.png';
|
||||
}
|
||||
|
||||
const coin = getCoinByAnyIdentifier(identifier);
|
||||
if (coin && coin.icon) {
|
||||
return coin.icon;
|
||||
}
|
||||
|
||||
const capitalizedName = identifier.toString().split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join('%20');
|
||||
|
||||
return `${capitalizedName}.png`;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -152,6 +152,21 @@ const ConfigManager = (function() {
|
||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filterCoin.includes(' ') || offerCoin.includes(' ')) {
|
||||
const filterFirstWord = filterCoin.split(' ')[0];
|
||||
const offerFirstWord = offerCoin.split(' ')[0];
|
||||
|
||||
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||
const filterHasCash = filterCoin.includes('cash');
|
||||
const offerHasCash = offerCoin.includes('cash');
|
||||
return filterHasCash === offerHasCash;
|
||||
}
|
||||
|
||||
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (particlVariants.includes(filterCoin)) {
|
||||
return offerCoin === filterCoin;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,6 @@ function initializeTableRateModule() {
|
||||
}
|
||||
|
||||
function continueInitialization() {
|
||||
updateCoinFilterImages();
|
||||
fetchOffers().then(() => {
|
||||
applyFilters();
|
||||
if (!isSentOffers) {
|
||||
@@ -157,65 +156,340 @@ function getValidOffers() {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filteredData = filterAndSortData();
|
||||
return filteredData;
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
function saveFilterSettings() {
|
||||
const formData = new FormData(filterForm);
|
||||
const filters = Object.fromEntries(formData);
|
||||
|
||||
const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
|
||||
|
||||
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||
|
||||
const statusSelect = document.getElementById('status');
|
||||
const sentFromSelect = document.getElementById('sent_from');
|
||||
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
coin_to: filters.coin_to,
|
||||
coin_from: filters.coin_from,
|
||||
status: filters.status,
|
||||
sent_from: filters.sent_from,
|
||||
coin_to: selectedCoinTo,
|
||||
coin_from: selectedCoinFrom,
|
||||
status: statusSelect ? statusSelect.value : 'any',
|
||||
sent_from: sentFromSelect ? sentFromSelect.value : 'any',
|
||||
auto_accept_type: autoAcceptTypeSelect ? autoAcceptTypeSelect.value : 'any',
|
||||
sortColumn: currentSortColumn,
|
||||
sortDirection: currentSortDirection
|
||||
}));
|
||||
}
|
||||
|
||||
function coinMatches(offerCoin, filterCoin) {
|
||||
if (!offerCoin || !filterCoin || filterCoin === 'any') return true;
|
||||
|
||||
offerCoin = offerCoin.toLowerCase();
|
||||
filterCoin = filterCoin.toLowerCase();
|
||||
function getSelectedCoins(filterType) {
|
||||
|
||||
if (offerCoin === filterCoin) return true;
|
||||
|
||||
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
|
||||
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
|
||||
return true;
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
if (!dropdown) {
|
||||
return ['any'];
|
||||
}
|
||||
|
||||
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
|
||||
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
||||
return true;
|
||||
}
|
||||
const allCheckboxes = dropdown.querySelectorAll('input[type="checkbox"]');
|
||||
|
||||
if (particlVariants.includes(filterCoin)) {
|
||||
return offerCoin === filterCoin;
|
||||
}
|
||||
const selected = [];
|
||||
allCheckboxes.forEach((cb) => {
|
||||
if (cb.checked && cb.value !== 'any') {
|
||||
selected.push(cb.value);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
return selected.length > 0 ? selected : ['any'];
|
||||
}
|
||||
|
||||
function filterAndSortData() {
|
||||
const formData = new FormData(filterForm);
|
||||
const filters = Object.fromEntries(formData);
|
||||
function getCoinNameFromValue(value, filterType) {
|
||||
if (value === 'any') {
|
||||
return 'any';
|
||||
}
|
||||
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
if (!dropdown) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const checkbox = dropdown.querySelector(`input[value="${value}"]`);
|
||||
if (checkbox) {
|
||||
const label = checkbox.closest('label');
|
||||
const spans = label.querySelectorAll('span');
|
||||
|
||||
const coinSpan = spans[spans.length - 1];
|
||||
if (coinSpan) {
|
||||
const text = coinSpan.textContent.trim();
|
||||
|
||||
const cleanText = text.replace(/\s*\(clear all\)\s*/, '');
|
||||
return cleanText;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function getCoinImage(coinName) {
|
||||
return window.CoinManager.getCoinIcon(coinName);
|
||||
}
|
||||
|
||||
function updateFilterButtonText(filterType) {
|
||||
const selected = getSelectedCoins(filterType);
|
||||
const button = document.getElementById(`${filterType}_button`);
|
||||
const textSpan = document.getElementById(`${filterType}_text`);
|
||||
|
||||
if (!button || !textSpan) return;
|
||||
|
||||
if (selected.length === 0 || (selected.length === 1 && selected[0] === 'any')) {
|
||||
|
||||
const defaultText = filterType === 'coin_to' ?
|
||||
(isSentOffers ? 'Filter Receiving' : 'Filter Bids') :
|
||||
(isSentOffers ? 'Filter Sending' : 'Filter Offers');
|
||||
textSpan.textContent = defaultText;
|
||||
} else {
|
||||
const filterLabel = filterType === 'coin_to' ?
|
||||
(isSentOffers ? 'Receiving' : 'Bids') :
|
||||
(isSentOffers ? 'Sending' : 'Offers');
|
||||
textSpan.textContent = `Filter ${filterLabel} (${selected.length} selected)`;
|
||||
}
|
||||
|
||||
|
||||
button.style.width = '210px';
|
||||
}
|
||||
|
||||
function updateCoinBadges(filterType) {
|
||||
const selected = getSelectedCoins(filterType);
|
||||
const badgesContainer = document.getElementById(`${filterType}_badges`);
|
||||
const mainContainer = document.getElementById('selected_coins_container');
|
||||
|
||||
if (!badgesContainer) return;
|
||||
|
||||
badgesContainer.innerHTML = '';
|
||||
|
||||
if (selected.length > 0 && !(selected.length === 1 && selected[0] === 'any')) {
|
||||
selected.forEach(coinValue => {
|
||||
const coinName = getCoinNameFromValue(coinValue, filterType);
|
||||
const badge = document.createElement('span');
|
||||
|
||||
|
||||
const isBidsFilter = filterType === 'coin_to' && !isSentOffers;
|
||||
const isOffersFilter = filterType === 'coin_from' && !isSentOffers;
|
||||
const isReceivingFilter = filterType === 'coin_to' && isSentOffers;
|
||||
const isSendingFilter = filterType === 'coin_from' && isSentOffers;
|
||||
|
||||
let badgeClass = 'inline-flex items-center px-3 py-2 rounded-full text-sm font-medium mr-2 mb-1';
|
||||
if (isBidsFilter || isReceivingFilter) {
|
||||
badgeClass += ' bg-blue-500 text-white dark:bg-blue-600 dark:text-white';
|
||||
} else {
|
||||
badgeClass += ' bg-green-400 text-white dark:bg-green-500 dark:text-white';
|
||||
}
|
||||
|
||||
badge.className = badgeClass + ' cursor-pointer hover:opacity-80';
|
||||
|
||||
|
||||
const coinImage = getCoinImage(coinName);
|
||||
|
||||
badge.innerHTML = `
|
||||
<img src="/static/images/coins/${coinImage}" class="w-4 h-4 mr-1" alt="${coinName}" onerror="this.style.display='none'">
|
||||
<span>${coinName}</span>
|
||||
<button type="button" class="ml-1 text-current hover:text-red-500 focus:outline-none remove-coin-btn">
|
||||
<span class="sr-only">Remove ${coinName}</span>
|
||||
×
|
||||
</button>
|
||||
`;
|
||||
|
||||
CleanupManager.addListener(badge, 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeCoinFilter(filterType, coinValue);
|
||||
});
|
||||
|
||||
const closeBtn = badge.querySelector('.remove-coin-btn');
|
||||
if (closeBtn) {
|
||||
CleanupManager.addListener(closeBtn, 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeCoinFilter(filterType, coinValue);
|
||||
});
|
||||
}
|
||||
|
||||
badgesContainer.appendChild(badge);
|
||||
});
|
||||
|
||||
if (mainContainer) {
|
||||
mainContainer.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
|
||||
const otherType = filterType === 'coin_to' ? 'coin_from' : 'coin_to';
|
||||
const otherSelected = getSelectedCoins(otherType);
|
||||
if (otherSelected.length === 0 || (otherSelected.length === 1 && otherSelected[0] === 'any')) {
|
||||
if (mainContainer) {
|
||||
mainContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coinMatches(offerCoin, filterCoins) {
|
||||
if (!offerCoin || !filterCoins) return true;
|
||||
|
||||
if (typeof filterCoins === 'string') {
|
||||
filterCoins = [filterCoins];
|
||||
}
|
||||
|
||||
if (filterCoins.includes('any') || filterCoins.length === 0) return true;
|
||||
|
||||
const normalizedOfferCoin = offerCoin.toLowerCase().trim();
|
||||
|
||||
return filterCoins.some(filterCoin => {
|
||||
if (!filterCoin) return false;
|
||||
|
||||
const normalizedFilterCoin = filterCoin.toLowerCase().trim();
|
||||
|
||||
if (normalizedOfferCoin === normalizedFilterCoin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((normalizedOfferCoin === 'firo' || normalizedOfferCoin === 'zcoin') &&
|
||||
(normalizedFilterCoin === 'firo' || normalizedFilterCoin === 'zcoin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((normalizedOfferCoin === 'bitcoincash' && normalizedFilterCoin === 'bitcoin cash') ||
|
||||
(normalizedOfferCoin === 'bitcoin cash' && normalizedFilterCoin === 'bitcoincash')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
||||
if (normalizedFilterCoin === 'particl' && particlVariants.includes(normalizedOfferCoin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (particlVariants.includes(normalizedFilterCoin)) {
|
||||
return normalizedOfferCoin === normalizedFilterCoin;
|
||||
}
|
||||
|
||||
if (normalizedFilterCoin.includes(' ') || normalizedOfferCoin.includes(' ')) {
|
||||
const filterFirstWord = normalizedFilterCoin.split(' ')[0];
|
||||
const offerFirstWord = normalizedOfferCoin.split(' ')[0];
|
||||
|
||||
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||
const filterHasCash = normalizedFilterCoin.includes('cash');
|
||||
const offerHasCash = normalizedOfferCoin.includes('cash');
|
||||
return filterHasCash === offerHasCash;
|
||||
}
|
||||
|
||||
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDropdown(filterType) {
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
const button = document.getElementById(`${filterType}_button`);
|
||||
const container = dropdown?.closest('.dropdown-container');
|
||||
|
||||
if (dropdown && button) {
|
||||
const isVisible = dropdown.style.display !== 'none';
|
||||
hideAllDropdowns();
|
||||
|
||||
if (!isVisible) {
|
||||
const buttonRect = button.getBoundingClientRect();
|
||||
dropdown.style.position = 'fixed';
|
||||
dropdown.style.top = (buttonRect.bottom + 4) + 'px';
|
||||
dropdown.style.left = buttonRect.left + 'px';
|
||||
dropdown.style.width = buttonRect.width + 'px';
|
||||
dropdown.style.display = 'block';
|
||||
|
||||
if (container) {
|
||||
container.classList.add('open');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideDropdown(filterType) {
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
const container = dropdown?.closest('.dropdown-container');
|
||||
|
||||
if (dropdown) {
|
||||
dropdown.style.display = 'none';
|
||||
if (container) {
|
||||
container.classList.remove('open');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllDropdowns() {
|
||||
hideDropdown('coin_to');
|
||||
hideDropdown('coin_from');
|
||||
}
|
||||
|
||||
function handleCoinCheckboxChange(filterType, checkbox) {
|
||||
if (checkbox.value === 'any') {
|
||||
|
||||
if (checkbox.checked) {
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
if (dropdown) {
|
||||
const otherCheckboxes = dropdown.querySelectorAll('input[type="checkbox"]:not([value="any"])');
|
||||
otherCheckboxes.forEach(cb => cb.checked = false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
if (checkbox.checked) {
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
if (dropdown) {
|
||||
const anyCheckbox = dropdown.querySelector('input[value="any"]');
|
||||
if (anyCheckbox) anyCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateFilterButtonText(filterType);
|
||||
updateCoinBadges(filterType);
|
||||
applyFilters();
|
||||
updateClearFiltersButton();
|
||||
}
|
||||
|
||||
function removeCoinFilter(filterType, coinValue) {
|
||||
|
||||
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||
if (!dropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkbox = dropdown.querySelector(`input[value="${coinValue}"]`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
|
||||
|
||||
updateFilterButtonText(filterType);
|
||||
updateCoinBadges(filterType);
|
||||
applyFilters();
|
||||
updateClearFiltersButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window.removeCoinFilter = removeCoinFilter;
|
||||
|
||||
function filterAndSortData() {
|
||||
saveFilterSettings();
|
||||
|
||||
let filteredData = [...originalJsonData];
|
||||
|
||||
const sentFromFilter = filters.sent_from || 'any';
|
||||
const statusSelect = document.getElementById('status');
|
||||
const sentFromSelect = document.getElementById('sent_from');
|
||||
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||
|
||||
const sentFromFilter = sentFromSelect ? sentFromSelect.value : 'any';
|
||||
filteredData = filteredData.filter(offer => {
|
||||
const isMatch = sentFromFilter === 'public' ? offer.is_public :
|
||||
sentFromFilter === 'private' ? !offer.is_public :
|
||||
@@ -223,37 +497,43 @@ function filterAndSortData() {
|
||||
return isMatch;
|
||||
});
|
||||
|
||||
const autoAcceptTypeFilter = autoAcceptTypeSelect ? autoAcceptTypeSelect.value : 'any';
|
||||
if (autoAcceptTypeFilter !== 'any') {
|
||||
filteredData = filteredData.filter(offer => {
|
||||
const offerAutoAcceptType = offer.auto_accept_type !== undefined ? offer.auto_accept_type : 0;
|
||||
return offerAutoAcceptType === parseInt(autoAcceptTypeFilter);
|
||||
});
|
||||
}
|
||||
|
||||
filteredData = filteredData.filter(offer => {
|
||||
if (!isSentOffers && isOfferExpired(offer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.coin_to && filters.coin_to !== 'any') {
|
||||
const coinToSelect = document.getElementById('coin_to');
|
||||
const selectedOption = coinToSelect?.querySelector(`option[value="${filters.coin_to}"]`);
|
||||
const coinName = selectedOption?.textContent.trim();
|
||||
|
||||
if (coinName && !coinMatches(offer.coin_to, coinName)) {
|
||||
if (selectedCoinTo.length > 0 && !(selectedCoinTo.length === 1 && selectedCoinTo[0] === 'any')) {
|
||||
const coinNames = selectedCoinTo.map(value => getCoinNameFromValue(value, 'coin_to'));
|
||||
const matches = coinMatches(offer.coin_to, coinNames);
|
||||
if (!matches) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.coin_from && filters.coin_from !== 'any') {
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
const selectedOption = coinFromSelect?.querySelector(`option[value="${filters.coin_from}"]`);
|
||||
const coinName = selectedOption?.textContent.trim();
|
||||
|
||||
if (coinName && !coinMatches(offer.coin_from, coinName)) {
|
||||
if (selectedCoinFrom.length > 0 && !(selectedCoinFrom.length === 1 && selectedCoinFrom[0] === 'any')) {
|
||||
const coinNames = selectedCoinFrom.map(value => getCoinNameFromValue(value, 'coin_from'));
|
||||
const matches = coinMatches(offer.coin_from, coinNames);
|
||||
if (!matches) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSentOffers && filters.status && filters.status !== 'any') {
|
||||
if (isSentOffers && statusSelect && statusSelect.value !== 'any') {
|
||||
const isExpired = offer.expire_at <= Math.floor(Date.now() / 1000);
|
||||
const isRevoked = Boolean(offer.is_revoked);
|
||||
|
||||
let statusMatch = false;
|
||||
switch (filters.status) {
|
||||
switch (statusSelect.value) {
|
||||
case 'active':
|
||||
statusMatch = !isExpired && !isRevoked;
|
||||
break;
|
||||
@@ -554,6 +834,7 @@ function formatInitialData(data) {
|
||||
amount_negotiable: Boolean(offer.amount_negotiable),
|
||||
is_revoked: Boolean(offer.is_revoked),
|
||||
is_public: offer.is_public !== undefined ? Boolean(offer.is_public) : false,
|
||||
auto_accept_type: offer.auto_accept_type !== undefined ? Number(offer.auto_accept_type) : 0,
|
||||
unique_id: `${offer.offer_id}_${offer.created_at}_${offer.coin_from}_${offer.coin_to}`
|
||||
}));
|
||||
}
|
||||
@@ -721,28 +1002,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
|
||||
}
|
||||
|
||||
function updateCoinFilterImages() {
|
||||
const coinToSelect = document.getElementById('coin_to');
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
const coinToButton = document.getElementById('coin_to_button');
|
||||
const coinFromButton = document.getElementById('coin_from_button');
|
||||
|
||||
function updateButtonImage(select, button) {
|
||||
const selectedOption = select.options[select.selectedIndex];
|
||||
const imagePath = selectedOption.getAttribute('data-image');
|
||||
if (imagePath && select.value !== 'any') {
|
||||
button.style.backgroundImage = `url(${imagePath})`;
|
||||
button.style.backgroundSize = '25px 25px';
|
||||
button.style.backgroundPosition = 'center';
|
||||
button.style.backgroundRepeat = 'no-repeat';
|
||||
} else {
|
||||
button.style.backgroundImage = 'none';
|
||||
}
|
||||
button.style.minWidth = '25px';
|
||||
button.style.minHeight = '25px';
|
||||
}
|
||||
|
||||
updateButtonImage(coinToSelect, coinToButton);
|
||||
updateButtonImage(coinFromSelect, coinFromButton);
|
||||
return;
|
||||
}
|
||||
|
||||
function updateClearFiltersButton() {
|
||||
@@ -799,11 +1059,7 @@ function cleanupTable() {
|
||||
}
|
||||
|
||||
function handleNoOffersScenario() {
|
||||
const formData = new FormData(filterForm);
|
||||
const filters = Object.fromEntries(formData);
|
||||
const hasActiveFilters = filters.coin_to !== 'any' ||
|
||||
filters.coin_from !== 'any' ||
|
||||
(filters.status && filters.status !== 'any');
|
||||
const hasFilters = hasActiveFilters();
|
||||
|
||||
stopRefreshAnimation();
|
||||
|
||||
@@ -812,7 +1068,7 @@ function handleNoOffersScenario() {
|
||||
cleanupRow(row);
|
||||
});
|
||||
|
||||
if (hasActiveFilters) {
|
||||
if (hasFilters) {
|
||||
offersBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="9" class="text-center py-8">
|
||||
@@ -1726,13 +1982,8 @@ function applyFilters() {
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
filterForm.reset();
|
||||
|
||||
const selectElements = filterForm.querySelectorAll('select');
|
||||
selectElements.forEach(select => {
|
||||
select.value = 'any';
|
||||
const event = new Event('change', { bubbles: true });
|
||||
select.dispatchEvent(event);
|
||||
document.querySelectorAll('.coin-to-checkbox, .coin-from-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = checkbox.value === 'any';
|
||||
});
|
||||
|
||||
const statusSelect = document.getElementById('status');
|
||||
@@ -1740,6 +1991,22 @@ function clearFilters() {
|
||||
statusSelect.value = 'any';
|
||||
}
|
||||
|
||||
const sentFromSelect = document.getElementById('sent_from');
|
||||
if (sentFromSelect) {
|
||||
sentFromSelect.value = 'any';
|
||||
}
|
||||
|
||||
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||
if (autoAcceptTypeSelect) {
|
||||
autoAcceptTypeSelect.value = 'any';
|
||||
}
|
||||
|
||||
updateFilterButtonText('coin_to');
|
||||
updateFilterButtonText('coin_from');
|
||||
updateCoinBadges('coin_to');
|
||||
updateCoinBadges('coin_from');
|
||||
hideAllDropdowns();
|
||||
|
||||
jsonData = [...originalJsonData];
|
||||
currentPage = 1;
|
||||
|
||||
@@ -1747,21 +2014,25 @@ function clearFilters() {
|
||||
localStorage.removeItem(storageKey);
|
||||
|
||||
updateOffersTable();
|
||||
updateCoinFilterImages();
|
||||
updateClearFiltersButton();
|
||||
}
|
||||
|
||||
function hasActiveFilters() {
|
||||
const selectElements = filterForm.querySelectorAll('select');
|
||||
let hasChangedFilters = false;
|
||||
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||
|
||||
selectElements.forEach(select => {
|
||||
if (select.value !== 'any') {
|
||||
hasChangedFilters = true;
|
||||
}
|
||||
});
|
||||
const hasCoinToFilter = selectedCoinTo.length > 0 && !(selectedCoinTo.length === 1 && selectedCoinTo[0] === 'any');
|
||||
const hasCoinFromFilter = selectedCoinFrom.length > 0 && !(selectedCoinFrom.length === 1 && selectedCoinFrom[0] === 'any');
|
||||
|
||||
return hasChangedFilters;
|
||||
const statusSelect = document.getElementById('status');
|
||||
const sentFromSelect = document.getElementById('sent_from');
|
||||
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||
|
||||
const hasStatusFilter = statusSelect && statusSelect.value !== 'any';
|
||||
const hasSentFromFilter = sentFromSelect && sentFromSelect.value !== 'any';
|
||||
const hasAutoAcceptTypeFilter = autoAcceptTypeSelect && autoAcceptTypeSelect.value !== 'any';
|
||||
|
||||
return hasCoinToFilter || hasCoinFromFilter || hasStatusFilter || hasSentFromFilter || hasAutoAcceptTypeFilter;
|
||||
}
|
||||
|
||||
function formatTimeLeft(timestamp) {
|
||||
@@ -1791,13 +2062,6 @@ function getCoinSymbolLowercase(coin) {
|
||||
}
|
||||
}
|
||||
|
||||
function coinMatches(offerCoin, filterCoin) {
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.coinMatches(offerCoin, filterCoin);
|
||||
}
|
||||
return window.config.coinMatches(offerCoin, filterCoin);
|
||||
}
|
||||
|
||||
function getProfitColorClass(percentage) {
|
||||
const numericPercentage = parseFloat(percentage);
|
||||
if (numericPercentage > 0) return 'text-green-500';
|
||||
@@ -1872,25 +2136,51 @@ function initializeTableEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
const coinToSelect = document.getElementById('coin_to');
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
const coinToButton = document.getElementById('coin_to_button');
|
||||
const coinFromButton = document.getElementById('coin_from_button');
|
||||
const coinToDropdown = document.getElementById('coin_to_dropdown');
|
||||
const coinFromDropdown = document.getElementById('coin_from_dropdown');
|
||||
const statusSelect = document.getElementById('status');
|
||||
const sentFromSelect = document.getElementById('sent_from');
|
||||
|
||||
if (coinToSelect) {
|
||||
CleanupManager.addListener(coinToSelect, 'change', () => {
|
||||
applyFilters();
|
||||
updateCoinFilterImages();
|
||||
|
||||
if (coinToButton && coinToDropdown) {
|
||||
CleanupManager.addListener(coinToButton, 'click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleDropdown('coin_to');
|
||||
});
|
||||
|
||||
const coinToCheckboxes = coinToDropdown.querySelectorAll('.coin-to-checkbox');
|
||||
coinToCheckboxes.forEach(checkbox => {
|
||||
CleanupManager.addListener(checkbox, 'change', (e) => {
|
||||
handleCoinCheckboxChange('coin_to', e.target);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (coinFromSelect) {
|
||||
CleanupManager.addListener(coinFromSelect, 'change', () => {
|
||||
applyFilters();
|
||||
updateCoinFilterImages();
|
||||
|
||||
if (coinFromButton && coinFromDropdown) {
|
||||
CleanupManager.addListener(coinFromButton, 'click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleDropdown('coin_from');
|
||||
});
|
||||
|
||||
const coinFromCheckboxes = coinFromDropdown.querySelectorAll('.coin-from-checkbox');
|
||||
coinFromCheckboxes.forEach(checkbox => {
|
||||
CleanupManager.addListener(checkbox, 'change', (e) => {
|
||||
handleCoinCheckboxChange('coin_from', e.target);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CleanupManager.addListener(window, 'resize', () => {
|
||||
hideAllDropdowns();
|
||||
});
|
||||
|
||||
CleanupManager.addListener(window, 'scroll', () => {
|
||||
hideAllDropdowns();
|
||||
});
|
||||
|
||||
if (statusSelect) {
|
||||
CleanupManager.addListener(statusSelect, 'change', () => {
|
||||
applyFilters();
|
||||
@@ -1903,11 +2193,17 @@ function initializeTableEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||
if (autoAcceptTypeSelect) {
|
||||
CleanupManager.addListener(autoAcceptTypeSelect, 'change', () => {
|
||||
applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
const clearFiltersBtn = document.getElementById('clearFilters');
|
||||
if (clearFiltersBtn) {
|
||||
CleanupManager.addListener(clearFiltersBtn, 'click', () => {
|
||||
clearFilters();
|
||||
updateCoinFilterImages();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2133,7 +2429,12 @@ async function initializeTableAndData() {
|
||||
updateClearFiltersButton();
|
||||
initializeTableEvents();
|
||||
initializeTooltips();
|
||||
updateCoinFilterImages();
|
||||
|
||||
|
||||
updateFilterButtonText('coin_to');
|
||||
updateFilterButtonText('coin_from');
|
||||
updateCoinBadges('coin_to');
|
||||
updateCoinBadges('coin_from');
|
||||
|
||||
try {
|
||||
await fetchOffers();
|
||||
@@ -2152,7 +2453,23 @@ function loadSavedSettings() {
|
||||
if (saved) {
|
||||
const settings = JSON.parse(saved);
|
||||
|
||||
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
|
||||
['coin_to', 'coin_from'].forEach(filterType => {
|
||||
const savedCoins = settings[filterType];
|
||||
if (savedCoins && Array.isArray(savedCoins)) {
|
||||
|
||||
document.querySelectorAll(`.${filterType}-checkbox`).forEach(cb => cb.checked = false);
|
||||
|
||||
savedCoins.forEach(coinValue => {
|
||||
const checkbox = document.querySelector(`.${filterType}-checkbox[value="${coinValue}"]`);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
|
||||
updateFilterButtonText(filterType);
|
||||
updateCoinBadges(filterType);
|
||||
}
|
||||
});
|
||||
|
||||
['status', 'sent_from', 'auto_accept_type'].forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element && settings[id]) element.value = settings[id];
|
||||
});
|
||||
@@ -2308,7 +2625,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
filterForm.querySelectorAll('select').forEach(select => {
|
||||
CleanupManager.addListener(select, 'change', () => {
|
||||
applyFilters();
|
||||
updateCoinFilterImages();
|
||||
updateClearFiltersButton();
|
||||
});
|
||||
});
|
||||
@@ -2319,6 +2635,23 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
CleanupManager.addListener(clearFiltersBtn, 'click', clearFilters);
|
||||
}
|
||||
|
||||
CleanupManager.addListener(document, 'click', (e) => {
|
||||
const coinToDropdown = document.getElementById('coin_to_dropdown');
|
||||
const coinFromDropdown = document.getElementById('coin_from_dropdown');
|
||||
|
||||
if (coinToDropdown && coinToDropdown.style.display !== 'none') {
|
||||
if (!e.target.closest('#coin_to_button') && !e.target.closest('#coin_to_dropdown')) {
|
||||
hideDropdown('coin_to');
|
||||
}
|
||||
}
|
||||
|
||||
if (coinFromDropdown && coinFromDropdown.style.display !== 'none') {
|
||||
if (!e.target.closest('#coin_from_button') && !e.target.closest('#coin_from_dropdown')) {
|
||||
hideDropdown('coin_from');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const rowTimeInterval = setInterval(updateRowTimes, 30000);
|
||||
if (CleanupManager.registerResource) {
|
||||
CleanupManager.registerResource('rowTimeInterval', rowTimeInterval, () => {
|
||||
|
||||
@@ -389,7 +389,7 @@ const createSwapTableRow = async (swap) => {
|
||||
<div class="flex items-center justify-center">
|
||||
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||
<img class="h-12"
|
||||
src="/static/images/coins/${swap.coin_from.replace(' ', '-')}.png"
|
||||
src="/static/images/coins/${window.CoinManager.getCoinIcon(swap.coin_from)}"
|
||||
alt="${swap.coin_from}"
|
||||
onerror="this.src='/static/images/coins/default.png'">
|
||||
</span>
|
||||
@@ -398,7 +398,7 @@ const createSwapTableRow = async (swap) => {
|
||||
</svg>
|
||||
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||
<img class="h-12"
|
||||
src="/static/images/coins/${swap.coin_to.replace(' ', '-')}.png"
|
||||
src="/static/images/coins/${window.CoinManager.getCoinIcon(swap.coin_to)}"
|
||||
alt="${swap.coin_to}"
|
||||
onerror="this.src='/static/images/coins/default.png'">
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user