Websockets for new listings (real time) on network/your offers table + Fix potential JS memory leaks. (#187)

* Websockets for new listings (real time) on network/your offers table + Fix potential JS memory leaks.

* Fix typo

* JS: Cleanup

* JS: Merge functions + Cleanup

* ui Fix price refresh

* JS: Big cleanup / various fixes

* Fix pagination

* JS: Fix pricechart JS error.
This commit is contained in:
Gerlof van Ek 2024-12-17 19:58:41 +01:00 committed by GitHub
parent e39613f49d
commit ebcc4ccb06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1666 additions and 1323 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// Config // CONFIG
const config = { const config = {
apiKeys: getAPIKeys(), apiKeys: getAPIKeys(),
coins: [ coins: [
@ -45,7 +45,7 @@ function getAPIKeys() {
}; };
} }
// Utils // UTILS
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, ','),
@ -69,7 +69,7 @@ const utils = {
} }
}; };
// Error // ERROR
class AppError extends Error { class AppError extends Error {
constructor(message, type = 'AppError') { constructor(message, type = 'AppError') {
super(message); super(message);
@ -77,7 +77,7 @@ class AppError extends Error {
} }
} }
// Log // LOG
const logger = { const logger = {
log: (message) => console.log(`[AppLog] ${new Date().toISOString()}: ${message}`), log: (message) => console.log(`[AppLog] ${new Date().toISOString()}: ${message}`),
warn: (message) => console.warn(`[AppWarn] ${new Date().toISOString()}: ${message}`), warn: (message) => console.warn(`[AppWarn] ${new Date().toISOString()}: ${message}`),
@ -152,7 +152,7 @@ const api = {
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`);
@ -171,11 +171,11 @@ const api = {
}; };
}); });
console.log(`Transformed CoinGecko data:`, transformedData); //console.log(`Transformed CoinGecko data:`, transformedData);
cache.set(cacheKey, transformedData); cache.set(cacheKey, transformedData);
return transformedData; return transformedData;
} catch (error) { } catch (error) {
console.error(`Error fetching CoinGecko data:`, error); //console.error(`Error fetching CoinGecko data:`, error);
return { error: error.message }; return { error: error.message };
} }
}, },
@ -185,30 +185,30 @@ const api = {
coinSymbols = [coinSymbols]; coinSymbols = [coinSymbols];
} }
console.log(`Fetching historical data for coins: ${coinSymbols.join(', ')}`); //console.log(`Fetching historical data for coins: ${coinSymbols.join(', ')}`);
const results = {}; const results = {};
const fetchPromises = coinSymbols.map(async coin => { const fetchPromises = coinSymbols.map(async coin => {
const coinConfig = config.coins.find(c => c.symbol === coin); const coinConfig = config.coins.find(c => c.symbol === coin);
if (!coinConfig) { if (!coinConfig) {
console.error(`Coin configuration not found for ${coin}`); //console.error(`Coin configuration not found for ${coin}`);
return; return;
} }
if (coin === 'WOW') { if (coin === 'WOW') {
const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`; const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`;
console.log(`CoinGecko URL for WOW: ${url}`); //console.log(`CoinGecko URL for WOW: ${url}`);
try { try {
const response = await api.makePostRequest(url); const response = await api.makePostRequest(url);
if (response && response.prices) { if (response && response.prices) {
results[coin] = response.prices; results[coin] = response.prices;
} else { } else {
console.error(`Unexpected data structure for WOW:`, response); //console.error(`Unexpected data structure for WOW:`, response);
} }
} catch (error) { } catch (error) {
console.error(`Error fetching CoinGecko data for WOW:`, error); //console.error(`Error fetching CoinGecko data for WOW:`, error);
} }
} else { } else {
const resolution = config.resolutions[config.currentResolution]; const resolution = config.resolutions[config.currentResolution];
@ -219,31 +219,31 @@ const api = {
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") {
console.error(`API Error for ${coin}:`, response.Message); //console.error(`API Error for ${coin}:`, response.Message);
} else if (response.Data && response.Data.Data) { } else if (response.Data && response.Data.Data) {
results[coin] = response.Data; results[coin] = response.Data;
} else { } else {
console.error(`Unexpected data structure for ${coin}:`, response); //console.error(`Unexpected data structure for ${coin}:`, response);
} }
} catch (error) { } catch (error) {
console.error(`Error fetching CryptoCompare data for ${coin}:`, error); //console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
} }
} }
}); });
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;
} }
}; };
// Cache // CACHE
const cache = { const cache = {
set: (key, value, customTtl = null) => { set: (key, value, customTtl = null) => {
const item = { const item = {
@ -252,7 +252,7 @@ const cache = {
expiresAt: Date.now() + (customTtl || app.cacheTTL) expiresAt: Date.now() + (customTtl || app.cacheTTL)
}; };
localStorage.setItem(key, JSON.stringify(item)); localStorage.setItem(key, JSON.stringify(item));
console.log(`Cache set for ${key}, expires in ${(customTtl || app.cacheTTL) / 1000} seconds`); //console.log(`Cache set for ${key}, expires in ${(customTtl || app.cacheTTL) / 1000} seconds`);
}, },
get: (key) => { get: (key) => {
const itemStr = localStorage.getItem(key); const itemStr = localStorage.getItem(key);
@ -263,17 +263,17 @@ const cache = {
const item = JSON.parse(itemStr); const item = JSON.parse(itemStr);
const now = Date.now(); const now = Date.now();
if (now < item.expiresAt) { if (now < item.expiresAt) {
console.log(`Cache hit for ${key}, ${(item.expiresAt - now) / 1000} seconds remaining`); //console.log(`Cache hit for ${key}, ${(item.expiresAt - now) / 1000} seconds remaining`);
return { return {
value: item.value, value: item.value,
remainingTime: item.expiresAt - now remainingTime: item.expiresAt - now
}; };
} else { } else {
console.log(`Cache expired for ${key}`); //console.log(`Cache expired for ${key}`);
localStorage.removeItem(key); localStorage.removeItem(key);
} }
} catch (e) { } catch (e) {
console.error('Error parsing cache item:', e); //console.error('Error parsing cache item:', e);
localStorage.removeItem(key); localStorage.removeItem(key);
} }
return null; return null;
@ -287,7 +287,7 @@ const cache = {
localStorage.removeItem(key); localStorage.removeItem(key);
} }
}); });
console.log('Cache cleared'); //console.log('Cache cleared');
} }
}; };
@ -343,7 +343,7 @@ displayCoinData: (coin, data) => {
updateUI(false); 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);
} }
}, },
@ -488,7 +488,7 @@ displayCoinData: (coin, data) => {
} }
}; };
// Chart // CHART
const chartModule = { const chartModule = {
chart: null, chart: null,
currentCoin: 'BTC', currentCoin: 'BTC',
@ -680,7 +680,7 @@ const chartModule = {
prepareChartData: (coinSymbol, data) => { prepareChartData: (coinSymbol, data) => {
if (!data) { if (!data) {
console.error(`No data received for ${coinSymbol}`); //console.error(`No data received for ${coinSymbol}`);
return []; return [];
} }
@ -739,7 +739,7 @@ const chartModule = {
y: price y: price
})); }));
} else { } else {
console.error(`Unexpected data structure for ${coinSymbol}:`, data); //console.error(`Unexpected data structure for ${coinSymbol}:`, data);
return []; return [];
} }
@ -748,7 +748,7 @@ const chartModule = {
y: point.y y: point.y
})); }));
} catch (error) { } catch (error) {
console.error(`Error preparing chart data for ${coinSymbol}:`, error); //console.error(`Error preparing chart data for ${coinSymbol}:`, error);
return []; return [];
} }
}, },
@ -786,21 +786,21 @@ const chartModule = {
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})`); //console.log(`Using cached data for ${coinSymbol} (${config.currentResolution})`);
} else { } else {
console.log(`Fetching fresh data for ${coinSymbol} (${config.currentResolution})`); //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}`); //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)); //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}`);
@ -832,7 +832,7 @@ const chartModule = {
chartModule.chart.update('active'); chartModule.chart.update('active');
} else { } else {
console.error('Chart object not initialized'); //console.error('Chart object not initialized');
throw new Error('Chart object not initialized'); throw new Error('Chart object not initialized');
} }
@ -841,7 +841,7 @@ const chartModule = {
ui.updateLoadTimeAndCache(loadTime, cachedData); ui.updateLoadTimeAndCache(loadTime, cachedData);
} catch (error) { } catch (error) {
console.error(`Error updating chart for ${coinSymbol}:`, 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();
@ -849,14 +849,30 @@ const chartModule = {
}, },
showChartLoader: () => { showChartLoader: () => {
document.getElementById('chart-loader').classList.remove('hidden'); const loader = document.getElementById('chart-loader');
document.getElementById('coin-chart').classList.add('hidden'); const chart = document.getElementById('coin-chart');
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.remove('hidden');
chart.classList.add('hidden');
}, },
hideChartLoader: () => { hideChartLoader: () => {
document.getElementById('chart-loader').classList.add('hidden'); const loader = document.getElementById('chart-loader');
document.getElementById('coin-chart').classList.remove('hidden'); const chart = document.getElementById('coin-chart');
}
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.add('hidden');
chart.classList.remove('hidden');
},
}; };
Chart.register(chartModule.verticalLinePlugin); Chart.register(chartModule.verticalLinePlugin);
@ -928,7 +944,7 @@ const app = {
chartModule.initChart(); chartModule.initChart();
chartModule.showChartLoader(); chartModule.showChartLoader();
} else { } else {
console.warn('Chart container not found, skipping chart initialization'); //console.warn('Chart container not found, skipping chart initialization');
} }
console.log('Loading all coin data...'); console.log('Loading all coin data...');
@ -941,13 +957,13 @@ const app = {
} }
ui.setActiveContainer('btc-container'); ui.setActiveContainer('btc-container');
console.log('Setting up event listeners and initializations...'); //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('Error during initialization:', error); //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();
@ -959,7 +975,7 @@ 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) {
@ -974,24 +990,24 @@ const app = {
const cacheKey = `coinData_${coin.symbol}`; const cacheKey = `coinData_${coin.symbol}`;
cache.set(cacheKey, coinData); cache.set(cacheKey, coinData);
} else { } else {
console.warn(`No data found for ${coin.symbol}`); //console.warn(`No data found for ${coin.symbol}`);
} }
} }
} catch (error) { } catch (error) {
console.error('Error loading all coin data:', error); //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 { } finally {
console.log('All coin data loaded'); //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}...`);
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}`); //console.log(`Using cached data for ${coin.symbol}`);
data = cachedData.value; data = cachedData.value;
} else { } else {
try { try {
@ -1004,11 +1020,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}`); //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); //console.error(`Error fetching ${coin.symbol} data:`, error.message);
data = { data = {
error: error.message error: error.message
}; };
@ -1018,16 +1034,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}`); //console.log(`Data loaded for ${coin.symbol}`);
}, },
setupEventListeners: () => { setupEventListeners: () => {
console.log('Setting up event listeners...'); //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) {
container.addEventListener('click', () => { container.addEventListener('click', () => {
console.log(`${coin.symbol} container clicked`); //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') {
@ -1054,7 +1070,7 @@ const app = {
if (closeErrorButton) { if (closeErrorButton) {
closeErrorButton.addEventListener('click', ui.hideErrorMessage); closeErrorButton.addEventListener('click', ui.hideErrorMessage);
} }
console.log('Event listeners set up'); //console.log('Event listeners set up');
}, },
initAutoRefresh: () => { initAutoRefresh: () => {
@ -1090,7 +1106,7 @@ const app = {
earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt); earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt);
} }
} catch (error) { } catch (error) {
console.error(`Error parsing cached item ${key}:`, error); //console.error(`Error parsing cached item ${key}:`, error);
localStorage.removeItem(key); localStorage.removeItem(key);
} }
} }
@ -1149,7 +1165,7 @@ const app = {
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.error(`No data found for ${coin.symbol}`);
} }
} }
@ -1164,7 +1180,7 @@ const app = {
console.log('All data refreshed successfully'); console.log('All data refreshed successfully');
} catch (error) { } catch (error) {
console.error('Error refreshing all data:', error); //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();
@ -1231,7 +1247,7 @@ const app = {
}, },
startSpinAnimation: () => { startSpinAnimation: () => {
console.log('Starting spin animation on auto-refresh button'); //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');
@ -1242,7 +1258,7 @@ const app = {
}, },
stopSpinAnimation: () => { stopSpinAnimation: () => {
console.log('Stopping spin animation on auto-refresh button'); //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');
@ -1250,7 +1266,7 @@ const app = {
}, },
updateLastRefreshedTime: () => { updateLastRefreshedTime: () => {
console.log('Updating last refreshed time'); //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();
@ -1268,43 +1284,43 @@ const app = {
}, },
updateBTCPrice: async () => { updateBTCPrice: async () => {
console.log('Updating BTC price...'); //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); //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); //console.error('Unexpected BTC data structure:', priceData);
app.btcPriceUSD = 0; app.btcPriceUSD = 0;
} }
} catch (error) { } catch (error) {
console.error('Error fetching BTC price:', error); //console.error('Error fetching BTC price:', error);
app.btcPriceUSD = 0; 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}`);
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`); //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."); //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`); 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."); //console.error("Sort icon not found.");
return; return;
} }
const sortOrder = sortIcon.textContent === '↓' ? 1 : -1; const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
@ -1318,7 +1334,7 @@ sortTable: (columnIndex) => {
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}"`); //console.log(`Comparing times: "${aValue}" vs "${bValue}"`);
const parseTime = (timeStr) => { const parseTime = (timeStr) => {
const [value, unit] = timeStr.split(' '); const [value, unit] = timeStr.split(' ');
@ -1337,7 +1353,7 @@ sortTable: (columnIndex) => {
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}"`); //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');
@ -1346,8 +1362,8 @@ sortTable: (columnIndex) => {
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('aCell:', aCell ? aCell.outerHTML : 'null');
console.log('bCell:', bCell ? bCell.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')) ||
@ -1359,7 +1375,7 @@ sortTable: (columnIndex) => {
aValue = aValue.toLowerCase(); aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase(); bValue = bValue.toLowerCase();
console.log(`Comparing trade actions: "${aValue}" vs "${bValue}"`); //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;
@ -1369,7 +1385,7 @@ sortTable: (columnIndex) => {
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}"`); //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'
@ -1381,9 +1397,9 @@ sortTable: (columnIndex) => {
if (tbody) { if (tbody) {
rows.forEach(row => tbody.appendChild(row)); rows.forEach(row => tbody.appendChild(row));
} else { } else {
console.error("Table body not found."); //console.error("Table body not found.");
} }
console.log('Sorting completed'); //console.log('Sorting completed');
}, },
initializeSelectImages: () => { initializeSelectImages: () => {
@ -1391,7 +1407,7 @@ sortTable: (columnIndex) => {
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}`); //console.error(`Elements not found for ${selectId}`);
return; return;
} }
const selectedOption = select.options[select.selectedIndex]; const selectedOption = select.options[select.selectedIndex];
@ -1418,7 +1434,7 @@ sortTable: (columnIndex) => {
select.addEventListener('change', handleSelectChange); select.addEventListener('change', handleSelectChange);
updateSelectedImage(selectId); updateSelectedImage(selectId);
} else { } else {
console.error(`Select element not found for ${selectId}`); //console.error(`Select element not found for ${selectId}`);
} }
}); });
}, },
@ -1445,7 +1461,7 @@ updateResolutionButtons: (coinSymbol) => {
}); });
}, },
toggleAutoRefresh: () => { toggleAutoRefresh: () => {
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());
@ -1480,4 +1496,12 @@ resolutionButtons.forEach(button => {
}); });
}); });
// LOAD
app.init(); app.init();
document.addEventListener('visibilitychange', () => {
if (!document.hidden && chartModule.chart) {
console.log('Page became visible, reinitializing chart');
chartModule.updateChart(chartModule.currentCoin, true);
}
});

View file

@ -15,6 +15,13 @@ function getAPIKeys() {
coinGecko: '{{coingecko_api_key}}' coinGecko: '{{coingecko_api_key}}'
}; };
} }
function getWebSocketConfig() {
return {
port: '{{ ws_port }}',
fallbackPort: '11700'
};
}
</script> </script>
{% if sent_offers %} {% if sent_offers %}
@ -300,13 +307,6 @@ function getAPIKeys() {
</button> </button>
</div> </div>
</div> </div>
<div class="w-full md:w-auto pt-3 px-3 hidden">
<div class="relative">
<button id="toggleView" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
<span>Toggle JSON View</span>
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -399,12 +399,19 @@ function getAPIKeys() {
<div class="rounded-b-md"> <div class="rounded-b-md">
<div class="w-full"> <div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400"> <div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center"> <div class="flex items-center">
<p class="text-sm font-heading dark:text-gray-400 mr-4">Last refreshed: <span id="lastRefreshTime">Never</span></p> <div class="flex items-center mr-4">
<p class="text-sm font-heading dark:text-gray-400 mr-4"><span class="ml-4" data-listing-label>Network Listings: </span><span id="newEntriesCount"></span></p> <span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<p class="text-sm font-heading dark:text-gray-400 mr-4"><span id="nextRefreshContainer" class="ml-4">Next refresh: <span id="nextRefreshTime"></span> <span id="status-text" class="text-sm text-gray-500">Connecting...</span>
</span></p> </div>
</div> <p class="text-sm font-heading dark:text-gray-400 mr-4">
Last refreshed: <span id="lastRefreshTime">Never</span>
</p>
<p class="text-sm font-heading dark:text-gray-400 mr-4">
<span data-listing-label>Network Listings: </span>
<span id="newEntriesCount"></span>
</p>
</div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button type="button" id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none"> <button type="button" id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
{{ page_back_svg | safe }} {{ page_back_svg | safe }}