ui Fix WOW chart and various fixes.

This commit is contained in:
gerlofvanek 2024-11-28 11:32:37 +01:00
parent 31ead537c9
commit 418e863d26

View file

@ -28,7 +28,7 @@ const config = {
} }
}, },
showVolume: false, showVolume: false,
cacheTTL: 5 * 60 * 1000, // 5 minutes in milliseconds cacheTTL: 5 * 60 * 1000, // 5 minutes
specialCoins: [''], specialCoins: [''],
resolutions: { resolutions: {
year: { days: 365, interval: 'month' }, year: { days: 365, interval: 'month' },
@ -126,56 +126,52 @@ const api = {
})); }));
}, },
fetchCoinGeckoDataXHR: async () => { fetchCoinGeckoDataXHR: async () => {
const cacheKey = 'coinGeckoOneLiner'; const cacheKey = 'coinGeckoOneLiner';
let cachedData = cache.get(cacheKey); let cachedData = cache.get(cacheKey);
if (cachedData) { if (cachedData) {
console.log('Using cached CoinGecko data'); console.log('Using cached CoinGecko data');
return cachedData.value; return cachedData.value;
} }
const coinIds = config.coins const coinIds = config.coins
.filter(coin => coin.usesCoinGecko) .filter(coin => coin.usesCoinGecko)
.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`; const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true`;
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 = Object.entries(data).map(([id, values]) => { const transformedData = {};
const coinConfig = config.coins.find(coin => coin.name === id); Object.entries(data).forEach(([id, values]) => {
return { const coinConfig = config.coins.find(coin => coin.name === id);
id, const symbol = coinConfig?.symbol.toLowerCase() || id;
symbol: coinConfig?.symbol.toLowerCase() || id, transformedData[symbol] = {
current_price: values.usd, current_price: values.usd,
price_btc: values.btc, price_btc: values.btc,
total_volume: values.usd_24h_vol, total_volume: values.usd_24h_vol,
price_change_percentage_24h: values.usd_24h_change, price_change_percentage_24h: values.usd_24h_change,
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;
} catch (error) {
return transformedData; console.error(`Error fetching CoinGecko data:`, error);
} catch (error) { return { error: error.message };
console.error(`Error fetching CoinGecko data:`, error); }
return { },
error: error.message
};
}
},
fetchHistoricalDataXHR: async (coinSymbols) => { fetchHistoricalDataXHR: async (coinSymbols) => {
if (!Array.isArray(coinSymbols)) { if (!Array.isArray(coinSymbols)) {
@ -237,7 +233,7 @@ const api = {
console.log('Final results object:', JSON.stringify(results, null, 2)); console.log('Final results object:', JSON.stringify(results, null, 2));
return results; return results;
}, }
}; };
// Cache // Cache
@ -290,49 +286,60 @@ const cache = {
// UI // UI
const ui = { const ui = {
displayCoinData: (coin, data) => { displayCoinData: (coin, data) => {
const coinConfig = config.coins.find(c => c.symbol === coin); const coinConfig = config.coins.find(c => c.symbol === coin);
let priceUSD, priceBTC, priceChange1d, volume24h; let priceUSD, priceBTC, priceChange1d, volume24h;
const updateUI = (isError = false) => { const updateUI = (isError = false) => {
const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`); const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`);
const volumeDiv = document.querySelector(`#${coin.toLowerCase()}-volume-div`); const volumeDiv = document.querySelector(`#${coin.toLowerCase()}-volume-div`);
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) {
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 && coin !== 'BTC') {
priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)} BTC`;
btcPriceDiv.style.display = 'flex';
}
ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
};
try {
if (data.error) {
throw new Error(data.error);
}
if (!data || !data.current_price) {
throw new Error(`Invalid CoinGecko data structure for ${coin}`);
}
priceUSD = data.current_price;
priceBTC = data.current_price / app.btcPriceUSD;
priceChange1d = data.price_change_percentage_24h;
volume24h = data.total_volume;
if (isNaN(priceUSD) || isNaN(priceBTC) || isNaN(volume24h)) { if (priceUsdElement) {
throw new Error(`Invalid numeric values in data for ${coin}`); priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`;
} }
updateUI(false);
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';
} else {
priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)} BTC`;
btcPriceDiv.style.display = 'flex';
}
}
ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
};
try {
if (data.error) {
throw new Error(data.error);
}
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) { } catch (error) {
console.error(`Error displaying data for ${coin}:`, error.message); console.error(`Error displaying data for ${coin}:`, error.message);
updateUI(true); updateUI(true);
} }
}, },
showLoader: () => { showLoader: () => {
const loader = document.getElementById('loader'); const loader = document.getElementById('loader');
@ -511,6 +518,11 @@ initChart: () => {
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)');
const formatTime = (date) => {
const hours = date.getHours().toString().padStart(2, '0');
return `${hours}:00`;
};
chartModule.chart = new Chart(ctx, { chartModule.chart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
@ -536,20 +548,37 @@ initChart: () => {
x: { x: {
type: 'time', type: 'time',
time: { time: {
unit: 'day', unit: 'hour',
displayFormats: { displayFormats: {
hour: 'ha', hour: 'HH:00',
day: 'MMM d' day: 'MMM d',
month: 'MMM yyyy'
} }
}, },
ticks: { ticks: {
source: 'data', source: 'data',
maxTicksLimit: 10, maxTicksLimit: 12,
font: { font: {
size: 12, size: 12,
family: "'Inter', sans-serif" family: "'Inter', sans-serif"
}, },
color: 'rgba(156, 163, 175, 1)' color: 'rgba(156, 163, 175, 1)',
callback: function(value) {
const date = new Date(value);
if (config.currentResolution === 'day') {
return formatTime(date);
} else if (config.currentResolution === 'year') {
return date.toLocaleDateString('en-US', {
month: 'short',
year: 'numeric'
});
} else {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
}
}
}, },
grid: { grid: {
display: false display: false
@ -588,15 +617,23 @@ initChart: () => {
callbacks: { callbacks: {
title: (tooltipItems) => { title: (tooltipItems) => {
const date = new Date(tooltipItems[0].parsed.x); const date = new Date(tooltipItems[0].parsed.x);
return date.toLocaleString('en-US', { if (config.currentResolution === 'day') {
month: 'short', return `${date.toLocaleDateString('en-US', {
day: 'numeric', month: 'short',
year: 'numeric', day: 'numeric'
hour: 'numeric', })} ${formatTime(date)}`;
minute: 'numeric', } else if (config.currentResolution === 'year') {
hour12: true, return date.toLocaleDateString('en-US', {
timeZone: 'UTC' year: 'numeric',
}); month: 'short',
day: 'numeric'
});
} else {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
}
}, },
label: (item) => { label: (item) => {
const value = item.parsed.y; const value = item.parsed.y;
@ -616,7 +653,6 @@ initChart: () => {
borderWidth: 1, borderWidth: 1,
radius: 2, radius: 2,
hoverRadius: 4, hoverRadius: 4,
hoverRadius: 4,
hitRadius: 6, hitRadius: 6,
hoverBorderWidth: 2 hoverBorderWidth: 2
}, },
@ -633,9 +669,7 @@ initChart: () => {
console.log('Chart initialized:', chartModule.chart); console.log('Chart initialized:', chartModule.chart);
}, },
prepareChartData: (coinSymbol, data) => { prepareChartData: (coinSymbol, data) => {
console.log(`Preparing chart data for ${coinSymbol}:`, JSON.stringify(data, null, 2));
if (!data) { if (!data) {
console.error(`No data received for ${coinSymbol}`); console.error(`No data received for ${coinSymbol}`);
return []; return [];
@ -644,7 +678,44 @@ initChart: () => {
try { try {
let preparedData; let preparedData;
if (data.Data && Array.isArray(data.Data)) { if (coinSymbol === 'WOW' && Array.isArray(data)) {
const endTime = new Date(data[data.length - 1][0]);
// Convert to local time
endTime.setMinutes(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.setMinutes(0, 0, 0);
const closestPoint = data.reduce((prev, curr) => {
const prevTime = new Date(prev[0]);
const currTime = new Date(curr[0]);
const prevDiff = Math.abs(prevTime - targetHour);
const currDiff = Math.abs(currTime - targetHour);
return currDiff < prevDiff ? curr : prev;
});
hourlyPoints.push({
x: targetHour,
y: closestPoint[1]
});
}
const lastTime = new Date(data[data.length - 1][0]);
if (lastTime.getMinutes() !== 0) {
hourlyPoints.push({
x: lastTime,
y: data[data.length - 1][1]
});
}
preparedData = hourlyPoints;
} else if (data.Data && Array.isArray(data.Data)) {
preparedData = data.Data.map(d => ({ preparedData = data.Data.map(d => ({
x: new Date(d.time * 1000), x: new Date(d.time * 1000),
y: d.close y: d.close
@ -664,7 +735,6 @@ initChart: () => {
return []; return [];
} }
console.log(`Prepared data for ${coinSymbol}:`, preparedData.slice(0, 5));
return preparedData; return preparedData;
} catch (error) { } catch (error) {
console.error(`Error preparing chart data for ${coinSymbol}:`, error); console.error(`Error preparing chart data for ${coinSymbol}:`, error);
@ -837,8 +907,8 @@ const app = {
disabled: 'Auto-refresh: disabled', disabled: 'Auto-refresh: disabled',
justRefreshed: 'Just refreshed', justRefreshed: 'Just refreshed',
}, },
cacheTTL: 15 * 60 * 1000, // 15 minutes in milliseconds cacheTTL: 5 * 60 * 1000, // 5 minutes
minimumRefreshInterval: 60 * 1000, // 1 minute in milliseconds minimumRefreshInterval: 60 * 1000, // 1 minute
init: () => { init: () => {
console.log('Initializing app...'); console.log('Initializing app...');
@ -889,31 +959,32 @@ const app = {
} }
}, },
loadAllCoinData: async () => { loadAllCoinData: async () => {
console.log('Loading data for all coins...'); 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) {
throw new Error(allCoinData.error); throw new Error(allCoinData.error);
} }
for (const coin of config.coins) { for (const coin of config.coins) {
const coinData = allCoinData.find(data => data.symbol.toUpperCase() === coin.symbol); const coinData = allCoinData[coin.symbol.toLowerCase()];
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 { } else {
console.error(`No data found for ${coin.symbol}`); 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('Error loading all coin data:', error);
ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
}
console.log('All coin data loaded');
},
loadCoinData: async (coin) => { loadCoinData: async (coin) => {
console.log(`Loading data for ${coin.symbol}...`); console.log(`Loading data for ${coin.symbol}...`);
@ -1021,7 +1092,6 @@ const app = {
} }
} catch (error) { } catch (error) {
console.error(`Error parsing cached item ${key}:`, error); console.error(`Error parsing cached item ${key}:`, error);
// Remove corrupted cache item
localStorage.removeItem(key); localStorage.removeItem(key);
} }
} }
@ -1047,40 +1117,65 @@ const app = {
app.updateNextRefreshTime(); app.updateNextRefreshTime();
}, },
refreshAllData: async () => { refreshAllData: async () => {
if (app.isRefreshing) { if (app.isRefreshing) {
console.log('Refresh already in progress, skipping...'); console.log('Refresh already in progress, skipping...');
return; return;
} }
console.log('Refreshing all data...'); console.log('Refreshing all data...');
app.isRefreshing = true; app.isRefreshing = true;
ui.showLoader(); ui.showLoader();
chartModule.showChartLoader(); chartModule.showChartLoader();
try {
cache.clear();
await app.updateBTCPrice();
await app.loadAllCoinData();
if (chartModule.currentCoin) {
await chartModule.updateChart(chartModule.currentCoin, true);
}
app.lastRefreshedTime = new Date(); try {
localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
ui.updateLastRefreshedTime(); cache.clear();
console.log('All data refreshed successfully');
} catch (error) { await app.updateBTCPrice();
console.error('Error refreshing all data:', error);
ui.displayErrorMessage('Failed to refresh all data. Please try again.'); const allCoinData = await api.fetchCoinGeckoDataXHR();
} finally { if (allCoinData.error) {
ui.hideLoader(); throw new Error(allCoinData.error);
chartModule.hideChartLoader(); }
app.isRefreshing = false;
if (app.isAutoRefreshEnabled) { for (const coin of config.coins) {
app.scheduleNextRefresh(); 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);
} 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: () => { updateNextRefreshTime: () => {
console.log('Updating next refresh time display'); console.log('Updating next refresh time display');
@ -1173,25 +1268,26 @@ const app = {
} }
}, },
updateBTCPrice: async () => { updateBTCPrice: async () => {
console.log('Updating BTC price...'); console.log('Updating BTC price...');
try { try {
const btcData = await api.fetchCoinGeckoDataXHR('bitcoin'); const priceData = await api.fetchCoinGeckoDataXHR();
if (btcData.error) { if (priceData.error) {
console.error('Error fetching BTC price:', btcData.error); console.error('Error fetching BTC price:', priceData.error);
app.btcPriceUSD = 0; app.btcPriceUSD = 0;
} else if (btcData[0] && btcData[0].current_price) { } else if (priceData.btc && priceData.btc.current_price) {
app.btcPriceUSD = btcData[0].current_price;
} else { app.btcPriceUSD = priceData.btc.current_price;
console.error('Unexpected BTC data structure:', btcData); } else {
app.btcPriceUSD = 0; console.error('Unexpected BTC data structure:', priceData);
} app.btcPriceUSD = 0;
} catch (error) { }
console.error('Error fetching BTC price:', error); } catch (error) {
app.btcPriceUSD = 0; console.error('Error fetching BTC price:', error);
} app.btcPriceUSD = 0;
console.log('Current BTC price:', app.btcPriceUSD); }
}, console.log('Current BTC price:', app.btcPriceUSD);
},
sortTable: (columnIndex) => { sortTable: (columnIndex) => {
console.log(`Sorting column: ${columnIndex}`); console.log(`Sorting column: ${columnIndex}`);