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