diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 4951d15..29e56b5 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -11349,6 +11349,11 @@ class BasicSwap(BaseApp, UIApp): if filter_include_sent is not None and filter_include_sent is not True: query_suffix += " AND was_sent = 0" + filter_auto_accept_type = filters.get("auto_accept_type", None) + if filter_auto_accept_type and filter_auto_accept_type != "any": + query_suffix += " AND auto_accept_type = :filter_auto_accept_type" + query_data["filter_auto_accept_type"] = int(filter_auto_accept_type) + query_suffix += getOrderByStr(filters) limit = filters.get("limit", None) diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 6761c3a..61f6fec 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -278,6 +278,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes: "is_public": o.addr_to == swap_client.network_addr or o.addr_to.strip() == "", } + offer_data["auto_accept_type"] = getattr(o, "auto_accept_type", 0) if with_extra_info: offer_data["amount_negotiable"] = o.amount_negotiable offer_data["rate_negotiable"] = o.rate_negotiable @@ -293,6 +294,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes: offer_data["feerate_to"] = o.to_feerate offer_data["automation_strat_id"] = getattr(o, "auto_accept_type", 0) + offer_data["auto_accept_type"] = getattr(o, "auto_accept_type", 0) if o.was_sent: try: diff --git a/basicswap/static/css/style.css b/basicswap/static/css/style.css index 8897efb..7c098c0 100644 --- a/basicswap/static/css/style.css +++ b/basicswap/static/css/style.css @@ -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; + } diff --git a/basicswap/static/js/amm_tables.js b/basicswap/static/js/amm_tables.js index f6545f9..f22802f 100644 --- a/basicswap/static/js/amm_tables.js +++ b/basicswap/static/js/amm_tables.js @@ -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) { diff --git a/basicswap/static/js/bids_available.js b/basicswap/static/js/bids_available.js index 4cd1e03..1120189 100644 --- a/basicswap/static/js/bids_available.js +++ b/basicswap/static/js/bids_available.js @@ -352,7 +352,7 @@ const createBidTableRow = async (bid) => {
@@ -361,7 +361,7 @@ const createBidTableRow = async (bid) => {
diff --git a/basicswap/static/js/bids_sentreceived.js b/basicswap/static/js/bids_sentreceived.js
index b15e2a4..c6f2110 100644
--- a/basicswap/static/js/bids_sentreceived.js
+++ b/basicswap/static/js/bids_sentreceived.js
@@ -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;
}
diff --git a/basicswap/static/js/modules/coin-manager.js b/basicswap/static/js/modules/coin-manager.js
index 773d5bf..a84db04 100644
--- a/basicswap/static/js/modules/coin-manager.js
+++ b/basicswap/static/js/modules/coin-manager.js
@@ -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`;
}
};
})();
diff --git a/basicswap/static/js/modules/config-manager.js b/basicswap/static/js/modules/config-manager.js
index d4df53e..7cd1bd3 100644
--- a/basicswap/static/js/modules/config-manager.js
+++ b/basicswap/static/js/modules/config-manager.js
@@ -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;
}
diff --git a/basicswap/static/js/offers.js b/basicswap/static/js/offers.js
index 37bc082..0873d5d 100644
--- a/basicswap/static/js/offers.js
+++ b/basicswap/static/js/offers.js
@@ -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 = `
+
@@ -398,7 +398,7 @@ const createSwapTableRow = async (swap) => {
diff --git a/basicswap/templates/bid.html b/basicswap/templates/bid.html
index 3bd66dc..71f967c 100644
--- a/basicswap/templates/bid.html
+++ b/basicswap/templates/bid.html
@@ -532,12 +532,12 @@