diff --git a/basicswap/static/js/offerstable.js b/basicswap/static/js/offerstable.js
index 2652780..2a595d6 100644
--- a/basicswap/static/js/offerstable.js
+++ b/basicswap/static/js/offerstable.js
@@ -449,33 +449,49 @@ const CacheManager = {
try {
this.cleanup();
+ if (!value) {
+ console.warn('Attempted to cache null/undefined value for key:', key);
+ return false;
+ }
+
const item = {
value: value,
timestamp: Date.now(),
expiresAt: Date.now() + (customTtl || CACHE_DURATION)
};
- const itemSize = new Blob([JSON.stringify(item)]).size;
- if (itemSize > this.maxSize) {
- //console.error(`Cache item exceeds maximum size (${(itemSize/1024/1024).toFixed(2)}MB)`);
+ try {
+ JSON.stringify(item);
+ } catch (e) {
+ console.error('Failed to serialize cache item:', e);
return false;
}
- localStorage.setItem(key, JSON.stringify(item));
- return true;
+ const itemSize = new Blob([JSON.stringify(item)]).size;
+ if (itemSize > this.maxSize) {
+ console.warn(`Cache item exceeds maximum size (${(itemSize/1024/1024).toFixed(2)}MB)`);
+ return false;
+ }
+
+ try {
+ localStorage.setItem(key, JSON.stringify(item));
+ return true;
+ } catch (storageError) {
+ if (storageError.name === 'QuotaExceededError') {
+ this.cleanup(true);
+ try {
+ localStorage.setItem(key, JSON.stringify(item));
+ return true;
+ } catch (retryError) {
+ console.error('Storage quota exceeded even after cleanup:', retryError);
+ return false;
+ }
+ }
+ throw storageError;
+ }
} catch (error) {
- if (error.name === 'QuotaExceededError') {
- this.cleanup(true); // Aggressive cleanup
- try {
- localStorage.setItem(key, JSON.stringify(item));
- return true;
- } catch (error) {
- console.error('Storage quota exceeded even after cleanup:', error.message);
- return false;
- }
- }
- //console.error('Cache set error:', error);
+ console.error('Cache set error:', error);
return false;
}
},
@@ -483,11 +499,26 @@ const CacheManager = {
get: function(key) {
try {
const itemStr = localStorage.getItem(key);
- if (!itemStr) return null;
+ if (!itemStr) {
+ return null;
+ }
+
+ let item;
+ try {
+ item = JSON.parse(itemStr);
+ } catch (parseError) {
+ console.error('Failed to parse cached item:', parseError);
+ localStorage.removeItem(key);
+ return null;
+ }
+
+ if (!item || typeof item.expiresAt !== 'number' || !item.hasOwnProperty('value')) {
+ console.warn('Invalid cache item structure for key:', key);
+ localStorage.removeItem(key);
+ return null;
+ }
- const item = JSON.parse(itemStr);
const now = Date.now();
-
if (now < item.expiresAt) {
return {
value: item.value,
@@ -496,11 +527,17 @@ const CacheManager = {
}
localStorage.removeItem(key);
+ return null;
+
} catch (error) {
- console.error("An error occured:", error.message);
- localStorage.removeItem(key);
+ console.error("Cache retrieval error:", error);
+ try {
+ localStorage.removeItem(key);
+ } catch (removeError) {
+ console.error("Failed to remove invalid cache entry:", removeError);
+ }
+ return null;
}
- return null;
},
cleanup: function(aggressive = false) {
@@ -533,7 +570,7 @@ const CacheManager = {
totalSize += size;
itemCount++;
} catch (error) {
- console.error("An error occured:", error.message);
+ console.error("Error processing cache item:", error);
localStorage.removeItem(key);
}
}
@@ -543,11 +580,21 @@ const CacheManager = {
while ((totalSize > this.maxSize || itemCount > this.maxItems) && items.length > 0) {
const item = items.pop();
- localStorage.removeItem(item.key);
- totalSize -= item.size;
- itemCount--;
+ try {
+ localStorage.removeItem(item.key);
+ totalSize -= item.size;
+ itemCount--;
+ } catch (error) {
+ console.error("Error removing cache item:", error);
+ }
}
}
+
+ return {
+ totalSize,
+ itemCount,
+ cleaned: items.length
+ };
},
clear: function() {
@@ -559,7 +606,13 @@ const CacheManager = {
}
}
- keys.forEach(key => localStorage.removeItem(key));
+ keys.forEach(key => {
+ try {
+ localStorage.removeItem(key);
+ } catch (error) {
+ console.error("Error clearing cache item:", error);
+ }
+ });
},
getStats: function() {
@@ -584,7 +637,7 @@ const CacheManager = {
expiredCount++;
}
} catch (error) {
- console.error("An error occured:", error.message);
+ console.error("Error getting cache stats:", error);
}
}
@@ -597,6 +650,8 @@ const CacheManager = {
}
};
+window.CacheManager = CacheManager;
+
// Identity cache management
const IdentityManager = {
cache: new Map(),
@@ -939,15 +994,44 @@ function filterAndSortData() {
comparison = a.offer_id.localeCompare(b.offer_id);
break;
}
-
return currentSortDirection === 'desc' ? -comparison : comparison;
});
}
-
//console.log(`[Debug] Filtered data length: ${filteredData.length}`);
return filteredData;
}
+function getPriceWithFallback(coin, latestPrices) {
+ const getPriceKey = (coin) => {
+ const lowerCoin = coin.toLowerCase();
+ if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
+ return 'zcoin';
+ }
+ if (lowerCoin === 'bitcoin cash') {
+ return 'bitcoin-cash';
+ }
+ if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') {
+ return 'particl';
+ }
+ return coinNameToSymbol[coin] || lowerCoin;
+ };
+
+ const priceKey = getPriceKey(coin);
+ const livePrice = latestPrices[priceKey]?.usd;
+ if (livePrice !== undefined && livePrice !== null) {
+ return livePrice;
+ }
+
+ if (window.tableRateModule) {
+ const fallback = window.tableRateModule.getFallbackValue(priceKey);
+ if (fallback !== null) {
+ return fallback;
+ }
+ }
+
+ return null;
+}
+
async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) {
return new Promise((resolve) => {
if (!latestPrices) {
@@ -972,26 +1056,33 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn
const fromSymbol = getPriceKey(fromCoin);
const toSymbol = getPriceKey(toCoin);
+ let fromPriceUSD = latestPrices[fromSymbol]?.usd;
+ let toPriceUSD = latestPrices[toSymbol]?.usd;
- const fromPriceUSD = latestPrices[fromSymbol]?.usd;
- const toPriceUSD = latestPrices[toSymbol]?.usd;
-
- if (fromPriceUSD === null || toPriceUSD === null ||
- fromPriceUSD === undefined || toPriceUSD === undefined) {
+ if (!fromPriceUSD || !toPriceUSD) {
+ fromPriceUSD = tableRateModule.getFallbackValue(fromSymbol);
+ toPriceUSD = tableRateModule.getFallbackValue(toSymbol);
+ }
+ if (!fromPriceUSD || !toPriceUSD || isNaN(fromPriceUSD) || isNaN(toPriceUSD)) {
resolve(null);
return;
}
-
const fromValueUSD = fromAmount * fromPriceUSD;
const toValueUSD = toAmount * toPriceUSD;
-
+ if (isNaN(fromValueUSD) || isNaN(toValueUSD) || fromValueUSD === 0 || toValueUSD === 0) {
+ resolve(null);
+ return;
+ }
let percentDiff;
if (isOwnOffer) {
percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100;
} else {
percentDiff = ((fromValueUSD / toValueUSD) - 1) * 100;
}
-
+ if (isNaN(percentDiff)) {
+ resolve(null);
+ return;
+ }
resolve(percentDiff);
});
}
@@ -1015,94 +1106,75 @@ function getEmptyPriceData() {
async function fetchLatestPrices() {
const PRICES_CACHE_KEY = 'prices_coingecko';
- const RETRY_DELAY = 5000;
- const MAX_RETRIES = 3;
- const cachedData = CacheManager.get(PRICES_CACHE_KEY);
- if (cachedData && cachedData.remainingTime > 30000) {
- console.log('Using cached price data');
- latestPrices = cachedData.value;
- return cachedData.value;
+ if (!window.isManualRefresh) {
+ const cachedData = CacheManager.get(PRICES_CACHE_KEY);
+ if (cachedData && cachedData.remainingTime > 60000) {
+ console.log('Using cached price data');
+ latestPrices = cachedData.value;
+ Object.entries(cachedData.value).forEach(([coin, prices]) => {
+ if (prices.usd) {
+ tableRateModule.setFallbackValue(coin, prices.usd);
+ }
+ });
+ return cachedData.value;
+ }
}
+ const url = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${offersConfig.apiKeys.coinGecko}`;
- const baseUrl = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zcoin,zano,wownero&vs_currencies=USD,BTC`;
+ try {
+ console.log('Initiating fresh price data fetch...');
+ const response = await fetch('/json/readurl', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ url: url,
+ headers: {}
+ })
+ });
- let retryCount = 0;
- let data = null;
-
- while (!data && retryCount < MAX_RETRIES) {
- if (retryCount > 0) {
- const delay = RETRY_DELAY * Math.pow(2, retryCount - 1);
- console.log(`Waiting ${delay}ms before retry ${retryCount + 1}...`);
- await new Promise(resolve => setTimeout(resolve, delay));
+ if (!response.ok) {
+ throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
- try {
- console.log('Attempting price fetch with API key...');
- const urlWithKey = `${baseUrl}&api_key=${offersConfig.apiKeys.coinGecko}`;
+ const data = await response.json();
- const response = await fetch('/json/readurl', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- url: urlWithKey,
- headers: {}
- })
+ if (data.Error) {
+ console.error('API Error:', data.Error);
+ throw new Error(data.Error);
+ }
+
+ if (data && Object.keys(data).length > 0) {
+ console.log('Processing fresh price data...');
+ latestPrices = data;
+ CacheManager.set(PRICES_CACHE_KEY, data, CACHE_DURATION);
+
+ Object.entries(data).forEach(([coin, prices]) => {
+ if (prices.usd) {
+ tableRateModule.setFallbackValue(coin, prices.usd);
+ }
});
- const responseData = await response.json();
-
- if (responseData.error) {
- if (responseData.error.includes('429')) {
- console.log('Rate limited, retrying...');
- } else {
- console.warn('Invalid price data received:', responseData);
- }
- retryCount++;
- continue;
- }
-
- const hasValidPrices = Object.values(responseData).some(coin =>
- coin && typeof coin === 'object' &&
- typeof coin.usd === 'number' &&
- !isNaN(coin.usd)
- );
-
- if (!hasValidPrices) {
- console.warn('No valid price data found in response');
- retryCount++;
- continue;
- }
-
- data = responseData;
- break;
-
- } catch (error) {
- console.warn('Error fetching prices:', error);
- retryCount++;
+ return data;
+ } else {
+ console.warn('No price data received');
+ return null;
}
+ } catch (error) {
+ console.error('Price Fetch Error:', error);
+ const fallbackPrices = {};
+ Object.keys(getEmptyPriceData()).forEach(coin => {
+ const fallbackValue = tableRateModule.getFallbackValue(coin);
+ if (fallbackValue !== null) {
+ fallbackPrices[coin] = { usd: fallbackValue, btc: null };
+ }
+ });
+ return Object.keys(fallbackPrices).length > 0 ? fallbackPrices : null;
+ } finally {
+ window.isManualRefresh = false;
}
-
- if (!data) {
- console.warn('All price fetch attempts failed, using empty price data');
- const naData = getEmptyPriceData();
- latestPrices = naData;
- return naData;
- }
-
- console.log('Successfully fetched fresh price data');
- latestPrices = data;
- CacheManager.set(PRICES_CACHE_KEY, data, CACHE_DURATION);
-
- Object.entries(data).forEach(([coin, prices]) => {
- if (prices && typeof prices.usd === 'number' && !isNaN(prices.usd)) {
- tableRateModule.setFallbackValue(coin, prices.usd);
- }
- });
-
- return data;
}
async function fetchOffers() {
@@ -1275,20 +1347,18 @@ function updatePaginationControls(totalPages) {
function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) {
const profitLossElement = row.querySelector('.profit-loss');
if (!profitLossElement) {
- //console.warn('Profit loss element not found in row');
return;
}
if (!fromCoin || !toCoin) {
- //console.error(`Invalid coin names: fromCoin=${fromCoin}, toCoin=${toCoin}`);
- profitLossElement.textContent = 'Error';
- profitLossElement.className = 'profit-loss text-lg font-bold text-red-500';
+ profitLossElement.textContent = 'N/A';
+ profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
return;
}
calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer)
.then(percentDiff => {
- if (percentDiff === null) {
+ if (percentDiff === null || isNaN(percentDiff)) {
profitLossElement.textContent = 'N/A';
profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
return;
@@ -1302,6 +1372,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
profitLossElement.textContent = `${percentDiffDisplay}%`;
profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`;
+ // Update tooltip if it exists
const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`;
const tooltipElement = document.getElementById(tooltipId);
if (tooltipElement) {
@@ -1316,8 +1387,8 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
})
.catch(error => {
console.error('Error in updateProfitLoss:', error);
- profitLossElement.textContent = 'Error';
- profitLossElement.className = 'profit-loss text-lg font-bold text-red-500';
+ profitLossElement.textContent = 'N/A';
+ profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
});
}
@@ -1802,18 +1873,27 @@ function createRateColumn(offer, coinFrom, coinTo) {
if (lowerCoin === 'bitcoin cash') {
return 'bitcoin-cash';
}
+ if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') {
+ return 'particl';
+ }
return coinNameToSymbol[coin] || lowerCoin;
};
- const toPriceUSD = latestPrices[getPriceKey(coinTo)]?.usd || 0;
- const rateInUSD = rate * toPriceUSD;
+ const toSymbolKey = getPriceKey(coinTo);
+ let toPriceUSD = latestPrices[toSymbolKey]?.usd;
+
+ if (!toPriceUSD || isNaN(toPriceUSD)) {
+ toPriceUSD = tableRateModule.getFallbackValue(toSymbolKey);
+ }
+
+ const rateInUSD = toPriceUSD && !isNaN(toPriceUSD) && !isNaN(rate) ? rate * toPriceUSD : null;
return `
- $${rateInUSD.toFixed(2)} USD
+ ${rateInUSD !== null ? `$${rateInUSD.toFixed(2)} USD` : 'N/A'}
${rate.toFixed(8)} ${toSymbol}/${fromSymbol}
@@ -2443,49 +2523,76 @@ function initializeTableEvents() {
});
}
- const refreshButton = document.getElementById('refreshOffers');
- if (refreshButton) {
- EventManager.add(refreshButton, 'click', async () => {
- console.log('Manual refresh initiated');
- const refreshIcon = document.getElementById('refreshIcon');
- const refreshText = document.getElementById('refreshText');
+const refreshButton = document.getElementById('refreshOffers');
+if (refreshButton) {
+ EventManager.add(refreshButton, 'click', async () => {
+ console.log('Manual refresh initiated');
+ const refreshIcon = document.getElementById('refreshIcon');
+ const refreshText = document.getElementById('refreshText');
- refreshButton.disabled = true;
- refreshIcon.classList.add('animate-spin');
- refreshText.textContent = 'Refreshing...';
- refreshButton.classList.add('opacity-75', 'cursor-wait');
+ refreshButton.disabled = true;
+ refreshIcon.classList.add('animate-spin');
+ refreshText.textContent = 'Refreshing...';
+ refreshButton.classList.add('opacity-75', 'cursor-wait');
- try {
- const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
- const response = await fetch(endpoint);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const newData = await response.json();
-
- const processedNewData = Array.isArray(newData) ? newData : Object.values(newData);
- console.log('Fetched offers:', processedNewData.length);
-
- jsonData = formatInitialData(processedNewData);
- originalJsonData = [...jsonData];
-
- await updateOffersTable();
- updateJsonView();
- updatePaginationInfo();
-
- console.log('Manual refresh completed successfully');
-
- } catch (error) {
- console.error('Error during manual refresh:', error);
- ui.displayErrorMessage('Failed to refresh offers. Please try again later.');
- } finally {
- refreshButton.disabled = false;
- refreshIcon.classList.remove('animate-spin');
- refreshText.textContent = 'Refresh';
- refreshButton.classList.remove('opacity-75', 'cursor-wait');
+ try {
+ const PRICES_CACHE_KEY = 'prices_coingecko';
+ localStorage.removeItem(PRICES_CACHE_KEY);
+ CacheManager.clear();
+ window.isManualRefresh = true;
+ const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
+ const response = await fetch(endpoint);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
}
- });
- }
+ const newData = await response.json();
+ const processedNewData = Array.isArray(newData) ? newData : Object.values(newData);
+ console.log('Fetched offers:', processedNewData.length);
+
+ jsonData = formatInitialData(processedNewData);
+ originalJsonData = [...jsonData];
+
+ const url = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${offersConfig.apiKeys.coinGecko}`;
+ console.log('Fetching fresh prices...');
+ const priceResponse = await fetch('/json/readurl', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url: url, headers: {} })
+ });
+
+ if (priceResponse.ok) {
+ const priceData = await priceResponse.json();
+ if (priceData && Object.keys(priceData).length > 0) {
+ console.log('Updating with fresh price data');
+ latestPrices = priceData;
+ CacheManager.set(PRICES_CACHE_KEY, priceData, CACHE_DURATION);
+ Object.entries(priceData).forEach(([coin, prices]) => {
+ if (prices.usd) {
+ tableRateModule.setFallbackValue(coin, prices.usd);
+ }
+ });
+ }
+ }
+
+ await updateOffersTable();
+ updateJsonView();
+ updatePaginationInfo();
+ lastRefreshTime = Date.now();
+ updateLastRefreshTime();
+
+ console.log('Manual refresh completed successfully');
+ } catch (error) {
+ console.error('Error during manual refresh:', error);
+ ui.displayErrorMessage('Failed to refresh offers. Please try again later.');
+ } finally {
+ window.isManualRefresh = false;
+ refreshButton.disabled = false;
+ refreshIcon.classList.remove('animate-spin');
+ refreshText.textContent = 'Refresh';
+ refreshButton.classList.remove('opacity-75', 'cursor-wait');
+ }
+ });
+}
document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
EventManager.add(header, 'click', () => {
@@ -2718,11 +2825,6 @@ async function cleanup() {
cleanupTable();
debug.addStep('Table cleanup completed', `Cleaned up ${rowCount} rows`);
- debug.addStep('Starting cache cleanup');
- const cacheStats = CacheManager.getStats();
- CacheManager.clear();
- debug.addStep('Cache cleanup completed', `Cleared ${cacheStats.itemCount} cached items`);
-
debug.addStep('Resetting global state');
const globals = {
currentPage: currentPage,
diff --git a/basicswap/static/js/pricechart.js b/basicswap/static/js/pricechart.js
index a4c63f2..0cc9163 100644
--- a/basicswap/static/js/pricechart.js
+++ b/basicswap/static/js/pricechart.js
@@ -42,7 +42,6 @@ const config = {
const utils = {
formatNumber: (number, decimals = 2) =>
number.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ','),
-
formatDate: (timestamp, resolution) => {
const date = new Date(timestamp);
const options = {
@@ -80,7 +79,6 @@ const logger = {
// API
const api = {
makePostRequest: (url, headers = {}) => {
- // unused // const apiKeys = getAPIKeys();
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/json/readurl');
@@ -141,17 +139,13 @@ const api = {
.map(coin => coin.name)
.join(',');
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
-
//console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
-
try {
const data = await api.makePostRequest(url);
//console.log(`Raw CoinGecko data:`, data);
-
if (typeof data !== 'object' || data === null) {
throw new AppError(`Invalid data structure received from CoinGecko`);
}
-
const transformedData = {};
Object.entries(data).forEach(([id, values]) => {
const coinConfig = config.coins.find(coin => coin.name === id);
@@ -164,7 +158,6 @@ const api = {
displayName: coinConfig?.displayName || coinConfig?.symbol || id
};
});
-
//console.log(`Transformed CoinGecko data:`, transformedData);
cache.set(cacheKey, transformedData);
return transformedData;
@@ -212,9 +205,7 @@ const api = {
} else {
url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`;
}
-
//console.log(`CryptoCompare URL for ${coin}: ${url}`);
-
try {
const response = await api.makePostRequest(url);
if (response.Response === "Error") {
@@ -229,9 +220,7 @@ const api = {
}
}
});
-
await Promise.all(fetchPromises);
-
//console.log('Final results object:', JSON.stringify(results, null, 2));
return results;
}
@@ -295,16 +284,13 @@ displayCoinData: (coin, data) => {
const volumeElement = document.querySelector(`#${coin.toLowerCase()}-volume-24h`);
const btcPriceDiv = document.querySelector(`#${coin.toLowerCase()}-btc-price-div`);
const priceBtcElement = document.querySelector(`#${coin.toLowerCase()}-price-btc`);
-
if (priceUsdElement) {
priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`;
}
-
if (volumeDiv && volumeElement) {
volumeElement.textContent = isError ? 'N/A' : `${utils.formatNumber(volume24h, 0)} USD`;
volumeDiv.style.display = volumeToggle.isVisible ? 'flex' : 'none';
}
-
if (btcPriceDiv && priceBtcElement) {
if (coin === 'BTC') {
btcPriceDiv.style.display = 'none';
@@ -313,10 +299,8 @@ displayCoinData: (coin, data) => {
btcPriceDiv.style.display = 'flex';
}
}
-
ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
};
-
try {
if (data.error) {
throw new Error(data.error);
@@ -324,19 +308,15 @@ displayCoinData: (coin, data) => {
if (!data || !data.current_price) {
throw new Error(`Invalid CoinGecko data structure for ${coin}`);
}
-
priceUSD = data.current_price;
priceBTC = coin === 'BTC' ? 1 : data.price_btc || (data.current_price / app.btcPriceUSD);
priceChange1d = data.price_change_percentage_24h;
volume24h = data.total_volume;
-
if (isNaN(priceUSD) || isNaN(priceBTC) || isNaN(volume24h)) {
throw new Error(`Invalid numeric values in data for ${coin}`);
}
-
updateUI(false);
} catch (error) {
- console.error(`Error displaying data for ${coin}:`, error.message);
updateUI(true);
}
},
@@ -381,11 +361,9 @@ displayCoinData: (coin, data) => {
updateLoadTimeAndCache: (loadTime, cachedData) => {
const loadTimeElement = document.getElementById('load-time');
const cacheStatusElement = document.getElementById('cache-status');
-
if (loadTimeElement) {
loadTimeElement.textContent = `Load time: ${loadTime}ms`;
}
-
if (cacheStatusElement) {
if (cachedData && cachedData.remainingTime) {
const remainingMinutes = Math.ceil(cachedData.remainingTime / 60000);
@@ -398,7 +376,6 @@ displayCoinData: (coin, data) => {
cacheStatusElement.classList.remove('text-green-500');
}
}
-
ui.updateLastRefreshedTime();
},
@@ -486,13 +463,6 @@ const chartModule = {
chart: null,
currentCoin: 'BTC',
loadStartTime: 0,
-
- cleanup: () => {
- if (chartModule.chart) {
- chartModule.chart.destroy();
- chartModule.chart = null;
- }
- },
verticalLinePlugin: {
id: 'verticalLine',
beforeDraw: (chart, args, options) => {
@@ -520,11 +490,9 @@ const chartModule = {
logger.error('Failed to get chart context. Make sure the canvas element exists.');
return;
}
-
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(77, 132, 240, 0.2)');
gradient.addColorStop(1, 'rgba(77, 132, 240, 0)');
-
chartModule.chart = new Chart(ctx, {
type: 'line',
data: {
@@ -674,12 +642,10 @@ const chartModule = {
plugins: [chartModule.verticalLinePlugin]
});
},
-
prepareChartData: (coinSymbol, data) => {
if (!data) {
return [];
}
-
try {
let preparedData;
@@ -688,13 +654,10 @@ const chartModule = {
endTime.setUTCMinutes(0, 0, 0);
const endUnix = endTime.getTime();
const startUnix = endUnix - (24 * 3600000);
-
const hourlyPoints = [];
-
for (let hourUnix = startUnix; hourUnix <= endUnix; hourUnix += 3600000) {
const targetHour = new Date(hourUnix);
targetHour.setUTCMinutes(0, 0, 0);
-
const closestPoint = data.reduce((prev, curr) => {
const prevTime = new Date(prev[0]);
const currTime = new Date(curr[0]);
@@ -737,13 +700,12 @@ const chartModule = {
} else {
return [];
}
-
return preparedData.map(point => ({
x: new Date(point.x).getTime(),
y: point.y
}));
} catch (error) {
- console.error("An error occured:", error.message);
+ console.error(`Error preparing chart data for ${coinSymbol}:`, error);
return [];
}
},
@@ -760,13 +722,11 @@ const chartModule = {
Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) <
Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev
);
-
hourlyData.push({
x: targetTime.getTime(),
y: closestDataPoint.y
});
}
-
return hourlyData;
},
@@ -774,24 +734,26 @@ const chartModule = {
try {
chartModule.showChartLoader();
chartModule.loadStartTime = Date.now();
-
const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`;
let cachedData = !forceRefresh ? cache.get(cacheKey) : null;
let data;
-
if (cachedData && Object.keys(cachedData.value).length > 0) {
data = cachedData.value;
+ //console.log(`Using cached data for ${coinSymbol} (${config.currentResolution})`);
} else {
+ //console.log(`Fetching fresh data for ${coinSymbol} (${config.currentResolution})`);
const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
data = allData[coinSymbol];
if (!data || Object.keys(data).length === 0) {
throw new Error(`No data returned for ${coinSymbol}`);
}
+ //console.log(`Caching new data for ${cacheKey}`);
cache.set(cacheKey, data, config.cacheTTL);
cachedData = null;
}
const chartData = chartModule.prepareChartData(coinSymbol, data);
+ //console.log(`Prepared chart data for ${coinSymbol}:`, chartData.slice(0, 5));
if (chartData.length === 0) {
throw new Error(`No valid chart data for ${coinSymbol}`);
@@ -807,11 +769,9 @@ const chartModule = {
} else {
const resolution = config.resolutions[config.currentResolution];
chartModule.chart.options.scales.x.time.unit = resolution.interval === 'hourly' ? 'hour' : 'day';
-
if (config.currentResolution === 'year' || config.currentResolution === 'sixMonths') {
chartModule.chart.options.scales.x.time.unit = 'month';
}
-
if (config.currentResolution === 'year') {
chartModule.chart.options.scales.x.ticks.maxTicksLimit = 12;
} else if (config.currentResolution === 'sixMonths') {
@@ -823,6 +783,7 @@ const chartModule = {
chartModule.chart.update('active');
} else {
+ //console.error('Chart object not initialized');
throw new Error('Chart object not initialized');
}
@@ -831,6 +792,7 @@ const chartModule = {
ui.updateLoadTimeAndCache(loadTime, cachedData);
} catch (error) {
+ //console.error(`Error updating chart for ${coinSymbol}:`, error);
ui.displayErrorMessage(`Failed to update chart for ${coinSymbol}: ${error.message}`);
} finally {
chartModule.hideChartLoader();
@@ -840,11 +802,10 @@ const chartModule = {
showChartLoader: () => {
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
-
if (!loader || !chart) {
+ //console.warn('Chart loader or chart container elements not found');
return;
}
-
loader.classList.remove('hidden');
chart.classList.add('hidden');
},
@@ -852,63 +813,49 @@ const chartModule = {
hideChartLoader: () => {
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
-
if (!loader || !chart) {
+ //console.warn('Chart loader or chart container elements not found');
return;
}
-
loader.classList.add('hidden');
chart.classList.remove('hidden');
- }
+ },
};
Chart.register(chartModule.verticalLinePlugin);
-const volumeToggle = {
- isVisible: localStorage.getItem('volumeToggleState') === 'true',
-
- cleanup: () => {
- const toggleButton = document.getElementById('toggle-volume');
- if (toggleButton) {
- toggleButton.removeEventListener('click', volumeToggle.toggle);
- }
- },
-
- init: () => {
- volumeToggle.cleanup();
-
- const toggleButton = document.getElementById('toggle-volume');
- if (toggleButton) {
- toggleButton.addEventListener('click', volumeToggle.toggle);
+ const volumeToggle = {
+ isVisible: localStorage.getItem('volumeToggleState') === 'true',
+ init: () => {
+ const toggleButton = document.getElementById('toggle-volume');
+ if (toggleButton) {
+ toggleButton.addEventListener('click', volumeToggle.toggle);
+ volumeToggle.updateVolumeDisplay();
+ }
+ },
+ toggle: () => {
+ volumeToggle.isVisible = !volumeToggle.isVisible;
+ localStorage.setItem('volumeToggleState', volumeToggle.isVisible.toString());
volumeToggle.updateVolumeDisplay();
+ },
+ updateVolumeDisplay: () => {
+ const volumeDivs = document.querySelectorAll('[id$="-volume-div"]');
+ volumeDivs.forEach(div => {
+ div.style.display = volumeToggle.isVisible ? 'flex' : 'none';
+ });
+ const toggleButton = document.getElementById('toggle-volume');
+ if (toggleButton) {
+ updateButtonStyles(toggleButton, volumeToggle.isVisible, 'green');
+ }
}
- },
+ };
- toggle: () => {
- volumeToggle.isVisible = !volumeToggle.isVisible;
- localStorage.setItem('volumeToggleState', volumeToggle.isVisible.toString());
- volumeToggle.updateVolumeDisplay();
- },
-
- updateVolumeDisplay: () => {
- const volumeDivs = document.querySelectorAll('[id$="-volume-div"]');
- volumeDivs.forEach(div => {
- div.style.display = volumeToggle.isVisible ? 'flex' : 'none';
- });
-
- const toggleButton = document.getElementById('toggle-volume');
- if (toggleButton) {
- updateButtonStyles(toggleButton, volumeToggle.isVisible, 'green');
- }
+ function updateButtonStyles(button, isActive, color) {
+ button.classList.toggle('text-' + color + '-500', isActive);
+ button.classList.toggle('text-gray-600', !isActive);
+ button.classList.toggle('dark:text-' + color + '-400', isActive);
+ button.classList.toggle('dark:text-gray-400', !isActive);
}
-};
-
-function updateButtonStyles(button, isActive, color) {
- button.classList.toggle('text-' + color + '-500', isActive);
- button.classList.toggle('text-gray-600', !isActive);
- button.classList.toggle('dark:text-' + color + '-400', isActive);
- button.classList.toggle('dark:text-gray-400', !isActive);
-}
const app = {
btcPriceUSD: 0,
@@ -924,177 +871,90 @@ const app = {
},
cacheTTL: 5 * 60 * 1000, // 5 minutes
minimumRefreshInterval: 60 * 1000, // 1 minute
- eventListeners: new Map(),
- visibilityCleanup: null,
-
- cleanup: () => {
- if (app.autoRefreshInterval) {
- clearTimeout(app.autoRefreshInterval);
- app.autoRefreshInterval = null;
- }
-
- if (app.updateNextRefreshTimeRAF) {
- cancelAnimationFrame(app.updateNextRefreshTimeRAF);
- app.updateNextRefreshTimeRAF = null;
- }
-
- if (typeof app.visibilityCleanup === 'function') {
- app.visibilityCleanup();
- app.visibilityCleanup = null;
- }
-
- volumeToggle.cleanup();
-
- app.removeEventListeners();
-
- if (chartModule.chart) {
- chartModule.chart.destroy();
- chartModule.chart = null;
- }
-
- cache.clear();
- },
-
- removeEventListeners: () => {
- app.eventListeners.forEach((listener, element) => {
- if (element && typeof element.removeEventListener === 'function') {
- element.removeEventListener(listener.type, listener.fn);
- }
- });
- app.eventListeners.clear();
- },
-
- addEventListenerWithCleanup: (element, type, fn) => {
- if (element && typeof element.addEventListener === 'function') {
- element.addEventListener(type, fn);
- app.eventListeners.set(element, { type, fn });
- }
- },
-
- initResolutionButtons: () => {
- const resolutionButtons = document.querySelectorAll('.resolution-button');
- resolutionButtons.forEach(button => {
- // Remove existing listeners first
- const oldListener = button.getAttribute('data-resolution-listener');
- if (oldListener && window[oldListener]) {
- button.removeEventListener('click', window[oldListener]);
- delete window[oldListener];
- }
-
- const listener = () => {
- const resolution = button.id.split('-')[1];
- const currentCoin = chartModule.currentCoin;
-
- if (currentCoin !== 'WOW' || resolution === 'day') {
- config.currentResolution = resolution;
- chartModule.updateChart(currentCoin, true);
- app.updateResolutionButtons(currentCoin);
- }
- };
-
- const listenerName = `resolutionListener_${button.id}`;
- window[listenerName] = listener;
- button.setAttribute('data-resolution-listener', listenerName);
- button.addEventListener('click', listener);
- });
- },
-
- setupVisibilityHandler: () => {
- const cleanup = () => {
- if (window.visibilityHandler) {
- document.removeEventListener('visibilitychange', window.visibilityHandler);
- delete window.visibilityHandler;
- }
- };
-
- cleanup();
-
- window.visibilityHandler = () => {
- if (!document.hidden && chartModule.chart) {
- chartModule.updateChart(chartModule.currentCoin, true);
- }
- };
-
- document.addEventListener('visibilitychange', window.visibilityHandler);
- return cleanup;
-},
init: () => {
console.log('Initializing app...');
- app.cleanup();
window.addEventListener('load', app.onLoad);
app.loadLastRefreshedTime();
app.updateAutoRefreshButton();
- app.initResolutionButtons();
- app.setupVisibilityHandler();
console.log('App initialized');
},
onLoad: async () => {
- console.log('App onLoad event triggered');
- ui.showLoader();
- try {
- volumeToggle.init();
- await app.updateBTCPrice();
- const chartContainer = document.getElementById('coin-chart');
- if (chartContainer) {
- chartModule.initChart();
- chartModule.showChartLoader();
- }
-
- console.log('Loading all coin data...');
- await app.loadAllCoinData();
-
- if (chartModule.chart) {
- config.currentResolution = 'day';
- await chartModule.updateChart('BTC');
- app.updateResolutionButtons('BTC');
- }
- ui.setActiveContainer('btc-container');
-
- app.setupEventListeners();
- app.initializeSelectImages();
- app.initAutoRefresh();
-
- } catch (error) {
- console.error("An error occured:", error.message);
- ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
- } finally {
- ui.hideLoader();
- if (chartModule.chart) {
- chartModule.hideChartLoader();
- }
- console.log('App onLoad completed');
+ console.log('App onLoad event triggered');
+ ui.showLoader();
+ try {
+ volumeToggle.init();
+ await app.updateBTCPrice();
+ const chartContainer = document.getElementById('coin-chart');
+ if (chartContainer) {
+ chartModule.initChart();
+ chartModule.showChartLoader();
+ } else {
+ //console.warn('Chart container not found, skipping chart initialization');
}
- },
- loadAllCoinData: async () => {
- try {
- const allCoinData = await api.fetchCoinGeckoDataXHR();
- if (allCoinData.error) {
- throw new Error(allCoinData.error);
- }
+ console.log('Loading all coin data...');
+ await app.loadAllCoinData();
- for (const coin of config.coins) {
- const coinData = allCoinData[coin.symbol.toLowerCase()];
- if (coinData) {
- coinData.displayName = coin.displayName || coin.symbol;
- ui.displayCoinData(coin.symbol, coinData);
- const cacheKey = `coinData_${coin.symbol}`;
- cache.set(cacheKey, coinData);
+ if (chartModule.chart) {
+ config.currentResolution = 'day';
+ await chartModule.updateChart('BTC');
+ app.updateResolutionButtons('BTC');
+ }
+ ui.setActiveContainer('btc-container');
+
+ //console.log('Setting up event listeners and initializations...');
+ app.setupEventListeners();
+ app.initializeSelectImages();
+ app.initAutoRefresh();
+
+ } catch (error) {
+ //console.error('Error during initialization:', error);
+ ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
+ } finally {
+ ui.hideLoader();
+ if (chartModule.chart) {
+ chartModule.hideChartLoader();
+ }
+ console.log('App onLoad completed');
+ }
+},
+
+ loadAllCoinData: async () => {
+ //console.log('Loading data for all coins...');
+ try {
+ const allCoinData = await api.fetchCoinGeckoDataXHR();
+ if (allCoinData.error) {
+ throw new Error(allCoinData.error);
+ }
+
+ for (const coin of config.coins) {
+ const coinData = allCoinData[coin.symbol.toLowerCase()];
+ if (coinData) {
+ coinData.displayName = coin.displayName || coin.symbol;
+ ui.displayCoinData(coin.symbol, coinData);
+ const cacheKey = `coinData_${coin.symbol}`;
+ cache.set(cacheKey, coinData);
+ } else {
+ //console.warn(`No data found for ${coin.symbol}`);
+ }
+ }
+ } catch (error) {
+ //console.error('Error loading all coin data:', error);
+ ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
+ } finally {
+ //console.log('All coin data loaded');
}
- }
- } catch (error) {
- console.error("An error occured:", error.message);
- ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
- }
- },
+ },
loadCoinData: async (coin) => {
+ //console.log(`Loading data for ${coin.symbol}...`);
const cacheKey = `coinData_${coin.symbol}`;
let cachedData = cache.get(cacheKey);
let data;
if (cachedData) {
+ //console.log(`Using cached data for ${coin.symbol}`);
data = cachedData.value;
} else {
try {
@@ -1107,9 +967,11 @@ const app = {
if (data.error) {
throw new Error(data.error);
}
+ //console.log(`Caching new data for ${coin.symbol}`);
cache.set(cacheKey, data);
cachedData = null;
} catch (error) {
+ //console.error(`Error fetching ${coin.symbol} data:`, error.message);
data = {
error: error.message
};
@@ -1119,13 +981,16 @@ const app = {
}
ui.displayCoinData(coin.symbol, data);
ui.updateLoadTimeAndCache(0, cachedData);
+ //console.log(`Data loaded for ${coin.symbol}`);
},
setupEventListeners: () => {
+ //console.log('Setting up event listeners...');
config.coins.forEach(coin => {
const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
if (container) {
- app.addEventListenerWithCleanup(container, 'click', () => {
+ container.addEventListener('click', () => {
+ //console.log(`${coin.symbol} container clicked`);
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
if (chartModule.chart) {
if (coin.symbol === 'WOW') {
@@ -1140,27 +1005,26 @@ const app = {
const refreshAllButton = document.getElementById('refresh-all');
if (refreshAllButton) {
- app.addEventListenerWithCleanup(refreshAllButton, 'click', app.refreshAllData);
+ refreshAllButton.addEventListener('click', app.refreshAllData);
}
const headers = document.querySelectorAll('th');
headers.forEach((header, index) => {
- app.addEventListenerWithCleanup(header, 'click', () =>
- app.sortTable(index, header.classList.contains('disabled'))
- );
+ header.addEventListener('click', () => app.sortTable(index, header.classList.contains('disabled')));
});
const closeErrorButton = document.getElementById('close-error');
if (closeErrorButton) {
- app.addEventListenerWithCleanup(closeErrorButton, 'click', ui.hideErrorMessage);
+ closeErrorButton.addEventListener('click', ui.hideErrorMessage);
}
+ //console.log('Event listeners set up');
},
initAutoRefresh: () => {
console.log('Initializing auto-refresh...');
const toggleAutoRefreshButton = document.getElementById('toggle-auto-refresh');
if (toggleAutoRefreshButton) {
- app.addEventListenerWithCleanup(toggleAutoRefreshButton, 'click', app.toggleAutoRefresh);
+ toggleAutoRefreshButton.addEventListener('click', app.toggleAutoRefresh);
app.updateAutoRefreshButton();
}
@@ -1189,7 +1053,7 @@ const app = {
earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt);
}
} catch (error) {
- console.error("An error occured:", error.message);
+ //console.error(`Error parsing cached item ${key}:`, error);
localStorage.removeItem(key);
}
}
@@ -1214,65 +1078,72 @@ const app = {
localStorage.setItem('nextRefreshTime', app.nextRefreshTime.toString());
app.updateNextRefreshTime();
},
-
- refreshAllData: async () => {
- if (app.isRefreshing) {
- console.log('Refresh already in progress, skipping...');
- return;
- }
-
- console.log('Refreshing all data...');
- app.isRefreshing = true;
- ui.showLoader();
- chartModule.showChartLoader();
-
- try {
- cache.clear();
- await app.updateBTCPrice();
-
- const allCoinData = await api.fetchCoinGeckoDataXHR();
- if (allCoinData.error) {
- throw new Error(allCoinData.error);
- }
-
- for (const coin of config.coins) {
- const symbol = coin.symbol.toLowerCase();
- const coinData = allCoinData[symbol];
- if (coinData) {
- coinData.displayName = coin.displayName || coin.symbol;
- ui.displayCoinData(coin.symbol, coinData);
- const cacheKey = `coinData_${coin.symbol}`;
- cache.set(cacheKey, coinData);
+
+ refreshAllData: async () => {
+ if (app.isRefreshing) {
+ console.log('Refresh already in progress, skipping...');
+ return;
}
- }
- if (chartModule.currentCoin) {
- await chartModule.updateChart(chartModule.currentCoin, true);
- }
+ console.log('Refreshing all data...');
+ app.isRefreshing = true;
+ ui.showLoader();
+ chartModule.showChartLoader();
+
+ try {
- app.lastRefreshedTime = new Date();
- localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
- ui.updateLastRefreshedTime();
+ cache.clear();
+
+ await app.updateBTCPrice();
+
+ const allCoinData = await api.fetchCoinGeckoDataXHR();
+ if (allCoinData.error) {
+ throw new Error(allCoinData.error);
+ }
+
+ for (const coin of config.coins) {
+ const symbol = coin.symbol.toLowerCase();
+ const coinData = allCoinData[symbol];
+ if (coinData) {
+ coinData.displayName = coin.displayName || coin.symbol;
- } catch (error) {
- console.error("An error occured:", error.message);
- ui.displayErrorMessage('Failed to refresh all data. Please try again.');
- } finally {
- ui.hideLoader();
- chartModule.hideChartLoader();
- app.isRefreshing = false;
- if (app.isAutoRefreshEnabled) {
- app.scheduleNextRefresh();
- }
- }
- },
+ ui.displayCoinData(coin.symbol, coinData);
+
+ const cacheKey = `coinData_${coin.symbol}`;
+ cache.set(cacheKey, coinData);
+ } else {
+ //console.error(`No data found for ${coin.symbol}`);
+ }
+ }
+
+ if (chartModule.currentCoin) {
+ await chartModule.updateChart(chartModule.currentCoin, true);
+ }
+
+ app.lastRefreshedTime = new Date();
+ localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
+ ui.updateLastRefreshedTime();
+
+ console.log('All data refreshed successfully');
+
+ } catch (error) {
+ //console.error('Error refreshing all data:', error);
+ ui.displayErrorMessage('Failed to refresh all data. Please try again.');
+ } finally {
+ ui.hideLoader();
+ chartModule.hideChartLoader();
+ app.isRefreshing = false;
+ if (app.isAutoRefreshEnabled) {
+ app.scheduleNextRefresh();
+ }
+ }
+ },
updateNextRefreshTime: () => {
console.log('Updating next refresh time display');
const nextRefreshSpan = document.getElementById('next-refresh-time');
const labelElement = document.getElementById('next-refresh-label');
const valueElement = document.getElementById('next-refresh-value');
-
if (nextRefreshSpan && labelElement && valueElement) {
if (app.nextRefreshTime) {
if (app.updateNextRefreshTimeRAF) {
@@ -1296,7 +1167,6 @@ const app = {
app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay);
}
};
-
updateDisplay();
} else {
labelElement.textContent = '';
@@ -1323,6 +1193,7 @@ const app = {
},
startSpinAnimation: () => {
+ //console.log('Starting spin animation on auto-refresh button');
const svg = document.querySelector('#toggle-auto-refresh svg');
if (svg) {
svg.classList.add('animate-spin');
@@ -1333,6 +1204,7 @@ const app = {
},
stopSpinAnimation: () => {
+ //console.log('Stopping spin animation on auto-refresh button');
const svg = document.querySelector('#toggle-auto-refresh svg');
if (svg) {
svg.classList.remove('animate-spin');
@@ -1340,6 +1212,7 @@ const app = {
},
updateLastRefreshedTime: () => {
+ //console.log('Updating last refreshed time');
const lastRefreshedElement = document.getElementById('last-refreshed-time');
if (lastRefreshedElement && app.lastRefreshedTime) {
const formattedTime = app.lastRefreshedTime.toLocaleTimeString();
@@ -1356,127 +1229,135 @@ const app = {
}
},
- updateBTCPrice: async () => {
- try {
- const priceData = await api.fetchCoinGeckoDataXHR();
- if (priceData.error) {
- app.btcPriceUSD = 0;
- } else if (priceData.btc && priceData.btc.current_price) {
- app.btcPriceUSD = priceData.btc.current_price;
- } else {
- app.btcPriceUSD = 0;
- }
- } catch (error) {
- console.error("An error occured:", error.message);
- app.btcPriceUSD = 0;
- }
- },
+ updateBTCPrice: async () => {
+ //console.log('Updating BTC price...');
+ try {
+ const priceData = await api.fetchCoinGeckoDataXHR();
+ if (priceData.error) {
+ //console.error('Error fetching BTC price:', priceData.error);
+ app.btcPriceUSD = 0;
+ } else if (priceData.btc && priceData.btc.current_price) {
- sortTable: (columnIndex) => {
- const sortableColumns = [0, 5, 6, 7]; // 0: Time, 5: Rate, 6: Market +/-, 7: Trade
- if (!sortableColumns.includes(columnIndex)) {
- return;
- }
-
- const table = document.querySelector('table');
- if (!table) {
- return;
- }
-
- const rows = Array.from(table.querySelectorAll('tbody tr'));
- const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
- if (!sortIcon) {
- return;
- }
-
- const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
- sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
-
- const getSafeTextContent = (element) => element ? element.textContent.trim() : '';
-
- rows.sort((a, b) => {
- let aValue, bValue;
- switch (columnIndex) {
- case 1: // Time column
- aValue = getSafeTextContent(a.querySelector('td:first-child .text-xs:first-child'));
- bValue = getSafeTextContent(b.querySelector('td:first-child .text-xs:first-child'));
-
- const parseTime = (timeStr) => {
- const [value, unit] = timeStr.split(' ');
- const numValue = parseFloat(value);
- switch(unit) {
- case 'seconds': return numValue;
- case 'minutes': return numValue * 60;
- case 'hours': return numValue * 3600;
- case 'days': return numValue * 86400;
- default: return 0;
+ app.btcPriceUSD = priceData.btc.current_price;
+ } else {
+ //console.error('Unexpected BTC data structure:', priceData);
+ app.btcPriceUSD = 0;
}
- };
- return (parseTime(bValue) - parseTime(aValue)) * sortOrder;
+ } catch (error) {
+ //console.error('Error fetching BTC price:', error);
+ app.btcPriceUSD = 0;
+ }
+ //console.log('Current BTC price:', app.btcPriceUSD);
+ },
- case 5: // Rate
- case 6: // Market +/-
- aValue = getSafeTextContent(a.cells[columnIndex]);
- bValue = getSafeTextContent(b.cells[columnIndex]);
+sortTable: (columnIndex) => {
+ //console.log(`Sorting column: ${columnIndex}`);
+ const sortableColumns = [0, 5, 6, 7]; // 0: Time, 5: Rate, 6: Market +/-, 7: Trade
+ if (!sortableColumns.includes(columnIndex)) {
+ //console.log(`Column ${columnIndex} is not sortable`);
+ return;
+ }
+ const table = document.querySelector('table');
+ if (!table) {
+ //console.error("Table not found for sorting.");
+ return;
+ }
+ const rows = Array.from(table.querySelectorAll('tbody tr'));
+ console.log(`Found ${rows.length} rows to sort`);
+ const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
+ if (!sortIcon) {
+ //console.error("Sort icon not found.");
+ return;
+ }
+ const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
+ sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
- aValue = parseFloat(aValue.replace(/[^\d.-]/g, '') || '0');
- bValue = parseFloat(bValue.replace(/[^\d.-]/g, '') || '0');
- return (aValue - bValue) * sortOrder;
+ const getSafeTextContent = (element) => element ? element.textContent.trim() : '';
- case 7: // Trade
- const aCell = a.cells[columnIndex];
- const bCell = b.cells[columnIndex];
+ rows.sort((a, b) => {
+ let aValue, bValue;
+ switch (columnIndex) {
+ case 1: // Time column
+ aValue = getSafeTextContent(a.querySelector('td:first-child .text-xs:first-child'));
+ bValue = getSafeTextContent(b.querySelector('td:first-child .text-xs:first-child'));
+ //console.log(`Comparing times: "${aValue}" vs "${bValue}"`);
- aValue = getSafeTextContent(aCell.querySelector('a')) ||
- getSafeTextContent(aCell.querySelector('button')) ||
- getSafeTextContent(aCell);
- bValue = getSafeTextContent(bCell.querySelector('a')) ||
- getSafeTextContent(bCell.querySelector('button')) ||
- getSafeTextContent(bCell);
+ const parseTime = (timeStr) => {
+ const [value, unit] = timeStr.split(' ');
+ const numValue = parseFloat(value);
+ switch(unit) {
+ case 'seconds': return numValue;
+ case 'minutes': return numValue * 60;
+ case 'hours': return numValue * 3600;
+ case 'days': return numValue * 86400;
+ default: return 0;
+ }
+ };
+ return (parseTime(bValue) - parseTime(aValue)) * sortOrder;
+
+ case 5: // Rate
+ case 6: // Market +/-
+ aValue = getSafeTextContent(a.cells[columnIndex]);
+ bValue = getSafeTextContent(b.cells[columnIndex]);
+ //console.log(`Comparing values: "${aValue}" vs "${bValue}"`);
- aValue = aValue.toLowerCase();
- bValue = bValue.toLowerCase();
-
- if (aValue === bValue) return 0;
- if (aValue === "swap") return -1 * sortOrder;
- if (bValue === "swap") return 1 * sortOrder;
- return aValue.localeCompare(bValue) * sortOrder;
-
- default:
- aValue = getSafeTextContent(a.cells[columnIndex]);
- bValue = getSafeTextContent(b.cells[columnIndex]);
- return aValue.localeCompare(bValue, undefined, {
- numeric: true,
- sensitivity: 'base'
- }) * sortOrder;
- }
- });
-
- const tbody = table.querySelector('tbody');
- if (tbody) {
- const fragment = document.createDocumentFragment();
- rows.forEach(row => fragment.appendChild(row));
- tbody.appendChild(fragment);
+ aValue = parseFloat(aValue.replace(/[^\d.-]/g, '') || '0');
+ bValue = parseFloat(bValue.replace(/[^\d.-]/g, '') || '0');
+ return (aValue - bValue) * sortOrder;
+
+ case 7: // Trade
+ const aCell = a.cells[columnIndex];
+ const bCell = b.cells[columnIndex];
+ //console.log('aCell:', aCell ? aCell.outerHTML : 'null');
+ //console.log('bCell:', bCell ? bCell.outerHTML : 'null');
+
+ aValue = getSafeTextContent(aCell.querySelector('a')) ||
+ getSafeTextContent(aCell.querySelector('button')) ||
+ getSafeTextContent(aCell);
+ bValue = getSafeTextContent(bCell.querySelector('a')) ||
+ getSafeTextContent(bCell.querySelector('button')) ||
+ getSafeTextContent(bCell);
+
+ aValue = aValue.toLowerCase();
+ bValue = bValue.toLowerCase();
+
+ //console.log(`Comparing trade actions: "${aValue}" vs "${bValue}"`);
+
+ if (aValue === bValue) return 0;
+ if (aValue === "swap") return -1 * sortOrder;
+ if (bValue === "swap") return 1 * sortOrder;
+ return aValue.localeCompare(bValue) * sortOrder;
+
+ default:
+ aValue = getSafeTextContent(a.cells[columnIndex]);
+ bValue = getSafeTextContent(b.cells[columnIndex]);
+ //console.log(`Comparing default values: "${aValue}" vs "${bValue}"`);
+ return aValue.localeCompare(bValue, undefined, {
+ numeric: true,
+ sensitivity: 'base'
+ }) * sortOrder;
}
- },
+ });
+ const tbody = table.querySelector('tbody');
+ if (tbody) {
+ rows.forEach(row => tbody.appendChild(row));
+ } else {
+ //console.error("Table body not found.");
+ }
+ //console.log('Sorting completed');
+},
+
initializeSelectImages: () => {
const updateSelectedImage = (selectId) => {
const select = document.getElementById(selectId);
const button = document.getElementById(`${selectId}_button`);
if (!select || !button) {
+ //console.error(`Elements not found for ${selectId}`);
return;
}
-
- const oldListener = select.getAttribute('data-change-listener');
- if (oldListener && window[oldListener]) {
- select.removeEventListener('change', window[oldListener]);
- delete window[oldListener];
- }
-
const selectedOption = select.options[select.selectedIndex];
const imageURL = selectedOption?.getAttribute('data-image');
-
requestAnimationFrame(() => {
if (imageURL) {
button.style.backgroundImage = `url('${imageURL}')`;
@@ -1490,50 +1371,46 @@ const app = {
button.style.minHeight = '25px';
});
};
-
+ const handleSelectChange = (event) => {
+ updateSelectedImage(event.target.id);
+ };
['coin_to', 'coin_from'].forEach(selectId => {
const select = document.getElementById(selectId);
if (select) {
-
- const listenerName = `selectChangeListener_${selectId}`;
- window[listenerName] = () => updateSelectedImage(selectId);
-
- select.setAttribute('data-change-listener', listenerName);
-
- select.addEventListener('change', window[listenerName]);
-
+ select.addEventListener('change', handleSelectChange);
updateSelectedImage(selectId);
- }
- });
- },
-
- updateResolutionButtons: (coinSymbol) => {
- const resolutionButtons = document.querySelectorAll('.resolution-button');
- resolutionButtons.forEach(button => {
- const resolution = button.id.split('-')[1];
- if (coinSymbol === 'WOW') {
- if (resolution === 'day') {
- button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
- button.classList.add('active');
- button.disabled = false;
- } else {
- button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
- button.classList.remove('active');
- button.disabled = true;
- }
} else {
- button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
- button.classList.toggle('active', resolution === config.currentResolution);
- button.disabled = false;
+ //console.error(`Select element not found for ${selectId}`);
}
});
},
- toggleAutoRefresh: () => {
+updateResolutionButtons: (coinSymbol) => {
+ const resolutionButtons = document.querySelectorAll('.resolution-button');
+ resolutionButtons.forEach(button => {
+ const resolution = button.id.split('-')[1];
+ if (coinSymbol === 'WOW') {
+ if (resolution === 'day') {
+ button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+ button.classList.add('active');
+ button.disabled = false;
+ } else {
+ button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+ button.classList.remove('active');
+ button.disabled = true;
+ }
+ } else {
+ button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+ button.classList.toggle('active', resolution === config.currentResolution);
+ button.disabled = false;
+ }
+ });
+},
+
+ toggleAutoRefresh: () => {
console.log('Toggling auto-refresh');
app.isAutoRefreshEnabled = !app.isAutoRefreshEnabled;
localStorage.setItem('autoRefreshEnabled', app.isAutoRefreshEnabled.toString());
-
if (app.isAutoRefreshEnabled) {
console.log('Auto-refresh enabled, scheduling next refresh');
app.scheduleNextRefresh();
@@ -1546,18 +1423,24 @@ const app = {
app.nextRefreshTime = null;
localStorage.removeItem('nextRefreshTime');
}
-
app.updateAutoRefreshButton();
app.updateNextRefreshTime();
}
};
+const resolutionButtons = document.querySelectorAll('.resolution-button');
+resolutionButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const resolution = button.id.split('-')[1];
+ const currentCoin = chartModule.currentCoin;
+
+ if (currentCoin !== 'WOW' || resolution === 'day') {
+ config.currentResolution = resolution;
+ chartModule.updateChart(currentCoin, true);
+ app.updateResolutionButtons(currentCoin);
+ }
+ });
+});
// LOAD
app.init();
-app.visibilityCleanup = app.setupVisibilityHandler();
-
-window.addEventListener('beforeunload', () => {
- console.log('Page unloading, cleaning up...');
- app.cleanup();
-});
|