mirror of
https://github.com/basicswap/basicswap.git
synced 2025-05-03 19:32:37 +00:00
Merge pull request #220 from nahuhh/pr/eslint
lint: eslinting suggestions
This commit is contained in:
commit
1cb8ffb632
5 changed files with 146 additions and 482 deletions
basicswap/static/js
|
@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
const image = selectedOption.getAttribute('data-image') || '';
|
const image = selectedOption.getAttribute('data-image') || '';
|
||||||
const name = selectedOption.textContent.trim();
|
const name = selectedOption.textContent.trim();
|
||||||
select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : '';
|
select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : '';
|
||||||
|
|
||||||
const selectImage = select.nextElementSibling.querySelector('.select-image');
|
const selectImage = select.nextElementSibling.querySelector('.select-image');
|
||||||
if (selectImage) {
|
if (selectImage) {
|
||||||
selectImage.src = image;
|
selectImage.src = image;
|
||||||
|
|
|
@ -19,8 +19,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const backdrop = document.querySelectorAll('.navbar-backdrop');
|
const backdrop = document.querySelectorAll('.navbar-backdrop');
|
||||||
|
|
||||||
if (close.length) {
|
if (close.length) {
|
||||||
for (var i = 0; i < close.length; i++) {
|
for (var k = 0; k < close.length; k++) {
|
||||||
close[i].addEventListener('click', function() {
|
close[k].addEventListener('click', function() {
|
||||||
for (var j = 0; j < menu.length; j++) {
|
for (var j = 0; j < menu.length; j++) {
|
||||||
menu[j].classList.toggle('hidden');
|
menu[j].classList.toggle('hidden');
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backdrop.length) {
|
if (backdrop.length) {
|
||||||
for (var i = 0; i < backdrop.length; i++) {
|
for (var l = 0; l < backdrop.length; l++) {
|
||||||
backdrop[i].addEventListener('click', function() {
|
backdrop[l].addEventListener('click', function() {
|
||||||
for (var j = 0; j < menu.length; j++) {
|
for (var j = 0; j < menu.length; j++) {
|
||||||
menu[j].classList.toggle('hidden');
|
menu[j].classList.toggle('hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
window.addEventListener('DOMContentLoaded', (event) => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
let err_msgs = document.querySelectorAll('p.error_msg');
|
const err_msgs = document.querySelectorAll('p.error_msg');
|
||||||
for (let i = 0; i < err_msgs.length; i++) {
|
for (let i = 0; i < err_msgs.length; i++) {
|
||||||
err_msg = err_msgs[i].innerText;
|
err_msg = err_msgs[i].innerText;
|
||||||
if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
|
if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
|
||||||
|
@ -29,9 +29,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove error class on input or select focus
|
// remove error class on input or select focus
|
||||||
let inputs = document.querySelectorAll('input.error');
|
const inputs = document.querySelectorAll('input.error');
|
||||||
let selects = document.querySelectorAll('select.error');
|
const selects = document.querySelectorAll('select.error');
|
||||||
let elements = [...inputs, ...selects];
|
const elements = [...inputs, ...selects];
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
element.addEventListener('focus', (event) => {
|
element.addEventListener('focus', (event) => {
|
||||||
event.target.classList.remove('error');
|
event.target.classList.remove('error');
|
||||||
|
|
|
@ -5,16 +5,16 @@ const EventManager = {
|
||||||
if (!this.listeners.has(element)) {
|
if (!this.listeners.has(element)) {
|
||||||
this.listeners.set(element, new Map());
|
this.listeners.set(element, new Map());
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementListeners = this.listeners.get(element);
|
const elementListeners = this.listeners.get(element);
|
||||||
if (!elementListeners.has(type)) {
|
if (!elementListeners.has(type)) {
|
||||||
elementListeners.set(type, new Set());
|
elementListeners.set(type, new Set());
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlerInfo = { handler, options };
|
const handlerInfo = { handler, options };
|
||||||
elementListeners.get(type).add(handlerInfo);
|
elementListeners.get(type).add(handlerInfo);
|
||||||
element.addEventListener(type, handler, options);
|
element.addEventListener(type, handler, options);
|
||||||
|
|
||||||
return handlerInfo;
|
return handlerInfo;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -74,14 +74,10 @@ let filterTimeout = null;
|
||||||
// CONFIGURATION CONSTANTS
|
// CONFIGURATION CONSTANTS
|
||||||
|
|
||||||
// Time Constants
|
// Time Constants
|
||||||
const MIN_REFRESH_INTERVAL = 60; // 60 sec
|
|
||||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||||
const FALLBACK_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
|
||||||
|
|
||||||
// Application Constants
|
// Application Constants
|
||||||
const itemsPerPage = 50;
|
const itemsPerPage = 50;
|
||||||
const PRICE_INIT_RETRIES = 3;
|
|
||||||
const PRICE_INIT_RETRY_DELAY = 2000;
|
|
||||||
const isSentOffers = window.offersTableConfig.isSentOffers;
|
const isSentOffers = window.offersTableConfig.isSentOffers;
|
||||||
|
|
||||||
const offersConfig = {
|
const offersConfig = {
|
||||||
|
@ -111,12 +107,6 @@ const coinNameToSymbol = {
|
||||||
'Bitcoin Cash': 'bitcoin-cash'
|
'Bitcoin Cash': 'bitcoin-cash'
|
||||||
};
|
};
|
||||||
|
|
||||||
const symbolToCoinName = {
|
|
||||||
...Object.fromEntries(Object.entries(coinNameToSymbol).map(([key, value]) => [value, key])),
|
|
||||||
'zcoin': 'Firo',
|
|
||||||
'firo': 'Firo'
|
|
||||||
};
|
|
||||||
|
|
||||||
const coinNameToDisplayName = {
|
const coinNameToDisplayName = {
|
||||||
'Bitcoin': 'Bitcoin',
|
'Bitcoin': 'Bitcoin',
|
||||||
'Litecoin': 'Litecoin',
|
'Litecoin': 'Litecoin',
|
||||||
|
@ -152,76 +142,6 @@ const totalPagesSpan = document.getElementById('totalPages');
|
||||||
const lastRefreshTimeSpan = document.getElementById('lastRefreshTime');
|
const lastRefreshTimeSpan = document.getElementById('lastRefreshTime');
|
||||||
const newEntriesCountSpan = document.getElementById('newEntriesCount');
|
const newEntriesCountSpan = document.getElementById('newEntriesCount');
|
||||||
|
|
||||||
const ScrollOptimizer = {
|
|
||||||
scrollTimeout: null,
|
|
||||||
isScrolling: false,
|
|
||||||
tooltipCache: new WeakMap(),
|
|
||||||
|
|
||||||
init() {
|
|
||||||
window.addEventListener('scroll', this.handleScroll.bind(this), { passive: true });
|
|
||||||
|
|
||||||
document.body.addEventListener('mouseenter', this.handleTooltipEnter.bind(this), true);
|
|
||||||
document.body.addEventListener('mouseleave', this.handleTooltipLeave.bind(this), true);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
if (this.scrollTimeout) {
|
|
||||||
window.cancelAnimationFrame(this.scrollTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isScrolling) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
document.body.classList.add('is-scrolling');
|
|
||||||
this.isScrolling = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollTimeout = window.requestAnimationFrame(() => {
|
|
||||||
document.body.classList.remove('is-scrolling');
|
|
||||||
this.isScrolling = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTooltipEnter(e) {
|
|
||||||
const tooltipTrigger = e.target.closest('[data-tooltip-target]');
|
|
||||||
if (!tooltipTrigger) return;
|
|
||||||
|
|
||||||
const tooltipId = tooltipTrigger.getAttribute('data-tooltip-target');
|
|
||||||
let tooltip = this.tooltipCache.get(tooltipTrigger);
|
|
||||||
|
|
||||||
if (!tooltip) {
|
|
||||||
tooltip = document.getElementById(tooltipId);
|
|
||||||
if (tooltip) {
|
|
||||||
this.tooltipCache.set(tooltipTrigger, tooltip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tooltip) {
|
|
||||||
tooltip.classList.remove('invisible', 'opacity-0');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTooltipLeave(e) {
|
|
||||||
const tooltipTrigger = e.target.closest('[data-tooltip-target]');
|
|
||||||
if (!tooltipTrigger) return;
|
|
||||||
|
|
||||||
const tooltip = this.tooltipCache.get(tooltipTrigger);
|
|
||||||
if (tooltip) {
|
|
||||||
tooltip.classList.add('invisible', 'opacity-0');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if (this.scrollTimeout) {
|
|
||||||
window.cancelAnimationFrame(this.scrollTimeout);
|
|
||||||
}
|
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
|
||||||
document.body.removeEventListener('mouseenter', this.handleTooltipEnter);
|
|
||||||
document.body.removeEventListener('mouseleave', this.handleTooltipLeave);
|
|
||||||
this.tooltipCache = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// MANAGER OBJECTS
|
// MANAGER OBJECTS
|
||||||
const WebSocketManager = {
|
const WebSocketManager = {
|
||||||
ws: null,
|
ws: null,
|
||||||
|
@ -259,7 +179,7 @@ const WebSocketManager = {
|
||||||
this.handlePageVisible();
|
this.handlePageVisible();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.handlers.visibilityChange);
|
document.addEventListener('visibilitychange', this.handlers.visibilityChange);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -550,8 +470,8 @@ const CacheManager = {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, JSON.stringify(item));
|
localStorage.setItem(key, JSON.stringify(item));
|
||||||
return true;
|
return true;
|
||||||
} catch (retryError) {
|
} catch (error) {
|
||||||
//console.error('Storage quota exceeded even after cleanup');
|
console.error('Storage quota exceeded even after cleanup:', error.message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -577,6 +497,7 @@ const CacheManager = {
|
||||||
|
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -612,6 +533,7 @@ const CacheManager = {
|
||||||
totalSize += size;
|
totalSize += size;
|
||||||
itemCount++;
|
itemCount++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -662,6 +584,7 @@ const CacheManager = {
|
||||||
expiredCount++;
|
expiredCount++;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,14 +644,15 @@ const IdentityManager = {
|
||||||
const response = await fetch(`/json/identities/${address}`, {
|
const response = await fetch(`/json/identities/${address}`, {
|
||||||
signal: AbortSignal.timeout(5000)
|
signal: AbortSignal.timeout(5000)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (attempt >= this.maxRetries) {
|
if (attempt >= this.maxRetries) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
|
console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -856,10 +780,6 @@ window.tableRateModule = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// CORE SYSTEM FUNCTIONS
|
// CORE SYSTEM FUNCTIONS
|
||||||
function initializeWebSocket() {
|
|
||||||
return WebSocketManager.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeTableRateModule() {
|
function initializeTableRateModule() {
|
||||||
if (typeof window.tableRateModule !== 'undefined') {
|
if (typeof window.tableRateModule !== 'undefined') {
|
||||||
tableRateModule = window.tableRateModule;
|
tableRateModule = window.tableRateModule;
|
||||||
|
@ -871,53 +791,12 @@ function initializeTableRateModule() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializePriceData() {
|
|
||||||
//console.log('Initializing price data...');
|
|
||||||
let retryCount = 0;
|
|
||||||
let prices = null;
|
|
||||||
|
|
||||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
|
||||||
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
|
||||||
if (cachedPrices && cachedPrices.value) {
|
|
||||||
console.log('Using cached price data');
|
|
||||||
latestPrices = cachedPrices.value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (retryCount < PRICE_INIT_RETRIES) {
|
|
||||||
try {
|
|
||||||
prices = await fetchLatestPrices();
|
|
||||||
|
|
||||||
if (prices && Object.keys(prices).length > 0) {
|
|
||||||
console.log('Successfully fetched initial price data');
|
|
||||||
latestPrices = prices;
|
|
||||||
CacheManager.set(PRICES_CACHE_KEY, prices, CACHE_DURATION);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
retryCount++;
|
|
||||||
|
|
||||||
if (retryCount < PRICE_INIT_RETRIES) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error fetching prices (attempt ${retryCount + 1}):`, error);
|
|
||||||
retryCount++;
|
|
||||||
|
|
||||||
if (retryCount < PRICE_INIT_RETRIES) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function continueInitialization() {
|
function continueInitialization() {
|
||||||
updateCoinFilterImages();
|
updateCoinFilterImages();
|
||||||
fetchOffers().then(() => {
|
fetchOffers().then(() => {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
if (!isSentOffers) {
|
if (!isSentOffers) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -928,32 +807,6 @@ function continueInitialization() {
|
||||||
//console.log('Initialization completed');
|
//console.log('Initialization completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkOfferAgainstFilters(offer, filters) {
|
|
||||||
if (filters.coin_to !== 'any' && !coinMatches(offer.coin_to, filters.coin_to)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (filters.coin_from !== 'any' && !coinMatches(offer.coin_from, filters.coin_from)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (filters.status && filters.status !== 'any') {
|
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
|
||||||
const isExpired = offer.expire_at <= currentTime;
|
|
||||||
const isRevoked = Boolean(offer.is_revoked);
|
|
||||||
|
|
||||||
switch (filters.status) {
|
|
||||||
case 'active':
|
|
||||||
return !isExpired && !isRevoked;
|
|
||||||
case 'expired':
|
|
||||||
return isExpired && !isRevoked;
|
|
||||||
case 'revoked':
|
|
||||||
return isRevoked;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeFlowbiteTooltips() {
|
function initializeFlowbiteTooltips() {
|
||||||
if (typeof Tooltip === 'undefined') {
|
if (typeof Tooltip === 'undefined') {
|
||||||
console.warn('Tooltip is not defined. Make sure the required library is loaded.');
|
console.warn('Tooltip is not defined. Make sure the required library is loaded.');
|
||||||
|
@ -971,65 +824,6 @@ function initializeFlowbiteTooltips() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATA PROCESSING FUNCTIONS
|
// DATA PROCESSING FUNCTIONS
|
||||||
async function checkExpiredAndFetchNew() {
|
|
||||||
if (isSentOffers) return Promise.resolve();
|
|
||||||
|
|
||||||
console.log('Starting checkExpiredAndFetchNew');
|
|
||||||
const OFFERS_CACHE_KEY = 'offers_received';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/json/offers');
|
|
||||||
const data = await response.json();
|
|
||||||
let newListings = Array.isArray(data) ? data : Object.values(data);
|
|
||||||
|
|
||||||
newListings = newListings.map(offer => ({
|
|
||||||
...offer,
|
|
||||||
offer_id: String(offer.offer_id || ''),
|
|
||||||
swap_type: String(offer.swap_type || 'N/A'),
|
|
||||||
addr_from: String(offer.addr_from || ''),
|
|
||||||
coin_from: String(offer.coin_from || ''),
|
|
||||||
coin_to: String(offer.coin_to || ''),
|
|
||||||
amount_from: String(offer.amount_from || '0'),
|
|
||||||
amount_to: String(offer.amount_to || '0'),
|
|
||||||
rate: String(offer.rate || '0'),
|
|
||||||
created_at: Number(offer.created_at || 0),
|
|
||||||
expire_at: Number(offer.expire_at || 0),
|
|
||||||
is_own_offer: Boolean(offer.is_own_offer),
|
|
||||||
amount_negotiable: Boolean(offer.amount_negotiable),
|
|
||||||
unique_id: `${offer.offer_id}_${offer.created_at}_${offer.coin_from}_${offer.coin_to}`
|
|
||||||
}));
|
|
||||||
|
|
||||||
newListings = newListings.filter(offer => !isOfferExpired(offer));
|
|
||||||
originalJsonData = newListings;
|
|
||||||
|
|
||||||
CacheManager.set(OFFERS_CACHE_KEY, newListings, CACHE_DURATION);
|
|
||||||
|
|
||||||
const currentFilters = new FormData(filterForm);
|
|
||||||
const hasActiveFilters = currentFilters.get('coin_to') !== 'any' ||
|
|
||||||
currentFilters.get('coin_from') !== 'any';
|
|
||||||
|
|
||||||
if (hasActiveFilters) {
|
|
||||||
jsonData = filterAndSortData();
|
|
||||||
} else {
|
|
||||||
jsonData = [...newListings];
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOffersTable();
|
|
||||||
updateJsonView();
|
|
||||||
updatePaginationInfo();
|
|
||||||
|
|
||||||
if (jsonData.length === 0) {
|
|
||||||
handleNoOffersScenario();
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonData.length;
|
|
||||||
} catch (error) {
|
|
||||||
//console.error('Error fetching new listings:', error);
|
|
||||||
nextRefreshCountdown = 60;
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValidOffers() {
|
function getValidOffers() {
|
||||||
if (!jsonData) {
|
if (!jsonData) {
|
||||||
//console.warn('jsonData is undefined or null');
|
//console.warn('jsonData is undefined or null');
|
||||||
|
@ -1182,7 +976,7 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn
|
||||||
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
||||||
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
||||||
|
|
||||||
if (fromPriceUSD === null || toPriceUSD === null ||
|
if (fromPriceUSD === null || toPriceUSD === null ||
|
||||||
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
|
@ -1202,42 +996,6 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMarketRate(fromCoin, toCoin) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
//console.log(`Attempting to get market rate for ${fromCoin} to ${toCoin}`);
|
|
||||||
if (!latestPrices) {
|
|
||||||
//console.warn('Latest prices object is not available');
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPriceKey = (coin) => {
|
|
||||||
const lowerCoin = coin.toLowerCase();
|
|
||||||
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
|
|
||||||
return 'zcoin';
|
|
||||||
}
|
|
||||||
if (lowerCoin === 'bitcoin cash') {
|
|
||||||
return 'bitcoin-cash';
|
|
||||||
}
|
|
||||||
return coinNameToSymbol[coin] || lowerCoin;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fromSymbol = getPriceKey(fromCoin);
|
|
||||||
const toSymbol = getPriceKey(toCoin);
|
|
||||||
|
|
||||||
const fromPrice = latestPrices[fromSymbol]?.usd;
|
|
||||||
const toPrice = latestPrices[toSymbol]?.usd;
|
|
||||||
if (!fromPrice || !toPrice) {
|
|
||||||
//console.warn(`Missing price data for ${!fromPrice ? fromCoin : toCoin}`);
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rate = toPrice / fromPrice;
|
|
||||||
//console.log(`Market rate calculated: ${rate} ${toCoin}/${fromCoin}`);
|
|
||||||
resolve(rate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEmptyPriceData() {
|
function getEmptyPriceData() {
|
||||||
return {
|
return {
|
||||||
'bitcoin': { usd: null, btc: null },
|
'bitcoin': { usd: null, btc: null },
|
||||||
|
@ -1259,7 +1017,7 @@ async function fetchLatestPrices() {
|
||||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
const PRICES_CACHE_KEY = 'prices_coingecko';
|
||||||
const RETRY_DELAY = 5000;
|
const RETRY_DELAY = 5000;
|
||||||
const MAX_RETRIES = 3;
|
const MAX_RETRIES = 3;
|
||||||
|
|
||||||
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
||||||
if (cachedData && cachedData.remainingTime > 30000) {
|
if (cachedData && cachedData.remainingTime > 30000) {
|
||||||
console.log('Using cached price data');
|
console.log('Using cached price data');
|
||||||
|
@ -1268,7 +1026,7 @@ async function fetchLatestPrices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
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`;
|
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 retryCount = 0;
|
||||||
let data = null;
|
let data = null;
|
||||||
|
|
||||||
|
@ -1282,7 +1040,7 @@ async function fetchLatestPrices() {
|
||||||
try {
|
try {
|
||||||
console.log('Attempting price fetch with API key...');
|
console.log('Attempting price fetch with API key...');
|
||||||
const urlWithKey = `${baseUrl}&api_key=${offersConfig.apiKeys.coinGecko}`;
|
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: {
|
||||||
|
@ -1306,9 +1064,9 @@ async function fetchLatestPrices() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasValidPrices = Object.values(responseData).some(coin =>
|
const hasValidPrices = Object.values(responseData).some(coin =>
|
||||||
coin && typeof coin === 'object' &&
|
coin && typeof coin === 'object' &&
|
||||||
typeof coin.usd === 'number' &&
|
typeof coin.usd === 'number' &&
|
||||||
!isNaN(coin.usd)
|
!isNaN(coin.usd)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1343,11 +1101,11 @@ async function fetchLatestPrices() {
|
||||||
tableRateModule.setFallbackValue(coin, prices.usd);
|
tableRateModule.setFallbackValue(coin, prices.usd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchOffers(manualRefresh = false) {
|
async function fetchOffers() {
|
||||||
const refreshButton = document.getElementById('refreshOffers');
|
const refreshButton = document.getElementById('refreshOffers');
|
||||||
const refreshIcon = document.getElementById('refreshIcon');
|
const refreshIcon = document.getElementById('refreshIcon');
|
||||||
const refreshText = document.getElementById('refreshText');
|
const refreshText = document.getElementById('refreshText');
|
||||||
|
@ -1557,7 +1315,7 @@ 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 = 'Error';
|
||||||
profitLossElement.className = 'profit-loss text-lg font-bold text-red-500';
|
profitLossElement.className = 'profit-loss text-lg font-bold text-red-500';
|
||||||
});
|
});
|
||||||
|
@ -1605,7 +1363,7 @@ function updateClearFiltersButton() {
|
||||||
|
|
||||||
function cleanupRow(row) {
|
function cleanupRow(row) {
|
||||||
EventManager.removeAll(row);
|
EventManager.removeAll(row);
|
||||||
|
|
||||||
const tooltips = row.querySelectorAll('[data-tooltip-target]');
|
const tooltips = row.querySelectorAll('[data-tooltip-target]');
|
||||||
tooltips.forEach(tooltip => {
|
tooltips.forEach(tooltip => {
|
||||||
const tooltipId = tooltip.getAttribute('data-tooltip-target');
|
const tooltipId = tooltip.getAttribute('data-tooltip-target');
|
||||||
|
@ -1618,7 +1376,7 @@ function cleanupRow(row) {
|
||||||
|
|
||||||
function cleanupTable() {
|
function cleanupTable() {
|
||||||
EventManager.clearAll();
|
EventManager.clearAll();
|
||||||
|
|
||||||
if (offersBody) {
|
if (offersBody) {
|
||||||
const existingRows = offersBody.querySelectorAll('tr');
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
existingRows.forEach(row => {
|
existingRows.forEach(row => {
|
||||||
|
@ -1686,13 +1444,13 @@ async function updateOffersTable() {
|
||||||
|
|
||||||
const BATCH_SIZE = 5;
|
const BATCH_SIZE = 5;
|
||||||
const identities = [];
|
const identities = [];
|
||||||
|
|
||||||
for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
|
for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
|
||||||
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
|
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
|
||||||
const batchPromises = batch.map(offer =>
|
const batchPromises = batch.map(offer =>
|
||||||
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
|
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
|
||||||
);
|
);
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises);
|
const batchResults = await Promise.all(batchPromises);
|
||||||
identities.push(...batchResults);
|
identities.push(...batchResults);
|
||||||
|
|
||||||
|
@ -1789,10 +1547,6 @@ function handleTableError() {
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIdentityData(address) {
|
|
||||||
return IdentityManager.getIdentityData(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIdentityInfo(address, identity) {
|
function getIdentityInfo(address, identity) {
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
return {
|
return {
|
||||||
|
@ -1928,10 +1682,6 @@ function createTimeColumn(offer, postedTime, expiresIn) {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldShowPublicTag(offers) {
|
|
||||||
return offers.some(offer => !offer.is_public);
|
|
||||||
}
|
|
||||||
|
|
||||||
function truncateText(text, maxLength = 15) {
|
function truncateText(text, maxLength = 15) {
|
||||||
if (typeof text !== 'string') return '';
|
if (typeof text !== 'string') return '';
|
||||||
return text.length > maxLength
|
return text.length > maxLength
|
||||||
|
@ -1977,7 +1727,7 @@ function createDetailsColumn(offer, identity = null) {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTakerAmountColumn(offer, coinTo, coinFrom) {
|
function createTakerAmountColumn(offer, coinTo) {
|
||||||
const fromAmount = parseFloat(offer.amount_to);
|
const fromAmount = parseFloat(offer.amount_to);
|
||||||
const toSymbol = getCoinSymbol(coinTo);
|
const toSymbol = getCoinSymbol(coinTo);
|
||||||
return `
|
return `
|
||||||
|
@ -2021,7 +1771,7 @@ function createSwapColumn(offer, coinFromDisplay, coinToDisplay, coinFromSymbol,
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOrderbookColumn(offer, coinFrom, coinTo) {
|
function createOrderbookColumn(offer, coinFrom) {
|
||||||
const toAmount = parseFloat(offer.amount_from);
|
const toAmount = parseFloat(offer.amount_from);
|
||||||
const fromSymbol = getCoinSymbol(coinFrom);
|
const fromSymbol = getCoinSymbol(coinFrom);
|
||||||
return `
|
return `
|
||||||
|
@ -2055,7 +1805,6 @@ function createRateColumn(offer, coinFrom, coinTo) {
|
||||||
return coinNameToSymbol[coin] || lowerCoin;
|
return coinNameToSymbol[coin] || lowerCoin;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromPriceUSD = latestPrices[getPriceKey(coinFrom)]?.usd || 0;
|
|
||||||
const toPriceUSD = latestPrices[getPriceKey(coinTo)]?.usd || 0;
|
const toPriceUSD = latestPrices[getPriceKey(coinTo)]?.usd || 0;
|
||||||
const rateInUSD = rate * toPriceUSD;
|
const rateInUSD = rate * toPriceUSD;
|
||||||
|
|
||||||
|
@ -2126,9 +1875,6 @@ function createActionColumn(offer, isActuallyExpired = false) {
|
||||||
|
|
||||||
// TOOLTIP FUNCTIONS
|
// TOOLTIP FUNCTIONS
|
||||||
function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired, isRevoked, identity = null) {
|
function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired, isRevoked, identity = null) {
|
||||||
const rate = parseFloat(offer.rate);
|
|
||||||
const fromSymbol = getCoinSymbolLowercase(coinFrom);
|
|
||||||
const toSymbol = getCoinSymbolLowercase(coinTo);
|
|
||||||
const uniqueId = `${offer.offer_id}_${offer.created_at}`;
|
const uniqueId = `${offer.offer_id}_${offer.created_at}`;
|
||||||
|
|
||||||
const addrFrom = offer.addr_from || '';
|
const addrFrom = offer.addr_from || '';
|
||||||
|
@ -2147,10 +1893,6 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
|
||||||
((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100
|
((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100
|
||||||
).toFixed(1) : 0;
|
).toFixed(1) : 0;
|
||||||
|
|
||||||
const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0;
|
|
||||||
const toPriceUSD = latestPrices[toSymbol]?.usd || 0;
|
|
||||||
const rateInUSD = rate * toPriceUSD;
|
|
||||||
|
|
||||||
const combinedRateTooltip = createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer);
|
const combinedRateTooltip = createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer);
|
||||||
const percentageTooltipContent = createTooltipContent(treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount);
|
const percentageTooltipContent = createTooltipContent(treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount);
|
||||||
|
|
||||||
|
@ -2329,8 +2071,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
|
||||||
|
|
||||||
const getPriceKey = (coin) => {
|
const getPriceKey = (coin) => {
|
||||||
const lowerCoin = coin.toLowerCase();
|
const lowerCoin = coin.toLowerCase();
|
||||||
return lowerCoin === 'firo' || lowerCoin === 'zcoin' ? 'zcoin' :
|
return lowerCoin === 'firo' || lowerCoin === 'zcoin' ? 'zcoin' :
|
||||||
lowerCoin === 'bitcoin cash' ? 'bitcoin-cash' :
|
lowerCoin === 'bitcoin cash' ? 'bitcoin-cash' :
|
||||||
lowerCoin === 'particl anon' || lowerCoin === 'particl blind' ? 'particl' :
|
lowerCoin === 'particl anon' || lowerCoin === 'particl blind' ? 'particl' :
|
||||||
coinNameToSymbol[coin] || lowerCoin;
|
coinNameToSymbol[coin] || lowerCoin;
|
||||||
};
|
};
|
||||||
|
@ -2340,7 +2082,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
|
||||||
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
||||||
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
||||||
|
|
||||||
if (fromPriceUSD === null || toPriceUSD === null ||
|
if (fromPriceUSD === null || toPriceUSD === null ||
|
||||||
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
||||||
return `<p class="font-bold mb-1">Price Information Unavailable</p>
|
return `<p class="font-bold mb-1">Price Information Unavailable</p>
|
||||||
<p>Current market prices are temporarily unavailable.</p>
|
<p>Current market prices are temporarily unavailable.</p>
|
||||||
|
@ -2421,7 +2163,7 @@ function createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer) {
|
||||||
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
const fromPriceUSD = latestPrices[fromSymbol]?.usd;
|
||||||
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
const toPriceUSD = latestPrices[toSymbol]?.usd;
|
||||||
|
|
||||||
if (fromPriceUSD === null || toPriceUSD === null ||
|
if (fromPriceUSD === null || toPriceUSD === null ||
|
||||||
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
fromPriceUSD === undefined || toPriceUSD === undefined) {
|
||||||
return `
|
return `
|
||||||
<p class="font-bold mb-1">Exchange Rate Information</p>
|
<p class="font-bold mb-1">Exchange Rate Information</p>
|
||||||
|
@ -2526,13 +2268,6 @@ function clearFilters() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasActiveFilters() {
|
function hasActiveFilters() {
|
||||||
const formData = new FormData(filterForm);
|
|
||||||
const filters = {
|
|
||||||
coin_to: formData.get('coin_to'),
|
|
||||||
coin_from: formData.get('coin_from'),
|
|
||||||
status: formData.get('status')
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectElements = filterForm.querySelectorAll('select');
|
const selectElements = filterForm.querySelectorAll('select');
|
||||||
let hasChangedFilters = false;
|
let hasChangedFilters = false;
|
||||||
|
|
||||||
|
@ -2622,20 +2357,6 @@ function isOfferExpired(offer) {
|
||||||
return isExpired;
|
return isExpired;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimeUntilNextExpiration() {
|
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
|
||||||
const nextExpiration = jsonData.reduce((earliest, offer) => {
|
|
||||||
const timeUntilExpiration = offer.expire_at - currentTime;
|
|
||||||
return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest;
|
|
||||||
}, Infinity);
|
|
||||||
|
|
||||||
return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300));
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateInverseRate(rate) {
|
|
||||||
return (1 / parseFloat(rate)).toFixed(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTime(timestamp, addAgoSuffix = false) {
|
function formatTime(timestamp, addAgoSuffix = false) {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const diff = Math.abs(now - timestamp);
|
const diff = Math.abs(now - timestamp);
|
||||||
|
@ -2796,47 +2517,6 @@ function initializeTableEvents() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventListeners = {
|
|
||||||
listeners: [],
|
|
||||||
|
|
||||||
add(element, eventType, handler, options = false) {
|
|
||||||
element.addEventListener(eventType, handler, options);
|
|
||||||
this.listeners.push({ element, eventType, handler, options });
|
|
||||||
// console.log(`Added ${eventType} listener to`, element);
|
|
||||||
},
|
|
||||||
|
|
||||||
addWindowListener(eventType, handler, options = false) {
|
|
||||||
window.addEventListener(eventType, handler, options);
|
|
||||||
this.listeners.push({ element: window, eventType, handler, options });
|
|
||||||
// console.log(`Added ${eventType} window listener`);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeAll() {
|
|
||||||
console.log('Removing all event listeners...');
|
|
||||||
this.listeners.forEach(({ element, eventType, handler, options }) => {
|
|
||||||
element.removeEventListener(eventType, handler, options);
|
|
||||||
//console.log(`Removed ${eventType} listener from`, element);
|
|
||||||
});
|
|
||||||
this.listeners = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
removeByElement(element) {
|
|
||||||
const remainingListeners = [];
|
|
||||||
this.listeners = this.listeners.filter(listener => {
|
|
||||||
if (listener.element === element) {
|
|
||||||
listener.element.removeEventListener(
|
|
||||||
listener.eventType,
|
|
||||||
listener.handler,
|
|
||||||
listener.options
|
|
||||||
);
|
|
||||||
console.log(`✂️ Removed ${listener.eventType} listener from`, element);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleTableSort(columnIndex, header) {
|
function handleTableSort(columnIndex, header) {
|
||||||
if (currentSortColumn === columnIndex) {
|
if (currentSortColumn === columnIndex) {
|
||||||
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
@ -2870,27 +2550,6 @@ function handleTableSort(columnIndex, header) {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupRowEventListeners(row, offer) {
|
|
||||||
const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]');
|
|
||||||
tooltipTriggers.forEach(trigger => {
|
|
||||||
EventManager.add(trigger, 'mouseenter', () => {
|
|
||||||
const tooltipId = trigger.getAttribute('data-tooltip-target');
|
|
||||||
const tooltip = document.getElementById(tooltipId);
|
|
||||||
if (tooltip) {
|
|
||||||
tooltip.classList.remove('invisible', 'opacity-0');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
EventManager.add(trigger, 'mouseleave', () => {
|
|
||||||
const tooltipId = trigger.getAttribute('data-tooltip-target');
|
|
||||||
const tooltip = document.getElementById(tooltipId);
|
|
||||||
if (tooltip) {
|
|
||||||
tooltip.classList.add('invisible', 'opacity-0');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TIMER MANAGEMENT
|
// TIMER MANAGEMENT
|
||||||
const timerManager = {
|
const timerManager = {
|
||||||
intervals: [],
|
intervals: [],
|
||||||
|
@ -3015,7 +2674,7 @@ async function cleanup() {
|
||||||
console.log(`Total cleanup time: ${totalTime}ms`);
|
console.log(`Total cleanup time: ${totalTime}ms`);
|
||||||
console.log('Steps completed:', this.steps.length);
|
console.log('Steps completed:', this.steps.length);
|
||||||
console.log('Errors encountered:', this.errors.length);
|
console.log('Errors encountered:', this.errors.length);
|
||||||
|
|
||||||
if (this.steps.length > 0) {
|
if (this.steps.length > 0) {
|
||||||
console.group('Steps Timeline');
|
console.group('Steps Timeline');
|
||||||
this.steps.forEach(({step, time}) => {
|
this.steps.forEach(({step, time}) => {
|
||||||
|
@ -3023,7 +2682,7 @@ async function cleanup() {
|
||||||
});
|
});
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.errors.length > 0) {
|
if (this.errors.length > 0) {
|
||||||
console.group('Errors');
|
console.group('Errors');
|
||||||
this.errors.forEach(({step, error, time}) => {
|
this.errors.forEach(({step, error, time}) => {
|
||||||
|
|
|
@ -40,9 +40,9 @@ const config = {
|
||||||
|
|
||||||
// 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, ','),
|
||||||
|
|
||||||
formatDate: (timestamp, resolution) => {
|
formatDate: (timestamp, resolution) => {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -80,7 +80,7 @@ const logger = {
|
||||||
// API
|
// API
|
||||||
const api = {
|
const api = {
|
||||||
makePostRequest: (url, headers = {}) => {
|
makePostRequest: (url, headers = {}) => {
|
||||||
const apiKeys = getAPIKeys();
|
// 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');
|
||||||
|
@ -114,7 +114,7 @@ const api = {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchCryptoCompareDataXHR: (coin) => {
|
fetchCryptoCompareDataXHR: (coin) => {
|
||||||
const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`;
|
const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`;
|
||||||
const headers = {
|
const headers = {
|
||||||
|
@ -126,10 +126,10 @@ const api = {
|
||||||
error: error.message
|
error: error.message
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchCoinGeckoDataXHR: async () => {
|
fetchCoinGeckoDataXHR: async () => {
|
||||||
const cacheKey = 'coinGeckoOneLiner';
|
const cacheKey = 'coinGeckoOneLiner';
|
||||||
let cachedData = cache.get(cacheKey);
|
const cachedData = cache.get(cacheKey);
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
console.log('Using cached CoinGecko data');
|
console.log('Using cached CoinGecko data');
|
||||||
|
@ -143,15 +143,15 @@ const api = {
|
||||||
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 +164,7 @@ 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;
|
||||||
|
@ -202,7 +202,7 @@ const api = {
|
||||||
//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];
|
||||||
|
@ -225,7 +225,7 @@ const api = {
|
||||||
//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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -266,8 +266,8 @@ const cache = {
|
||||||
//console.log(`Cache expired for ${key}`);
|
//console.log(`Cache expired for ${key}`);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
//console.error('Error parsing cache item:', e);
|
console.error('Error parsing cache item:', error.message);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -288,7 +288,6 @@ const cache = {
|
||||||
// UI
|
// UI
|
||||||
const ui = {
|
const ui = {
|
||||||
displayCoinData: (coin, data) => {
|
displayCoinData: (coin, data) => {
|
||||||
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`);
|
||||||
|
@ -296,16 +295,16 @@ 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';
|
||||||
|
@ -314,10 +313,10 @@ 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);
|
||||||
|
@ -325,51 +324,51 @@ 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);
|
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');
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loader.classList.remove('hidden');
|
loader.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideLoader: () => {
|
hideLoader: () => {
|
||||||
const loader = document.getElementById('loader');
|
const loader = document.getElementById('loader');
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loader.classList.add('hidden');
|
loader.classList.add('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showCoinLoader: (coinSymbol) => {
|
showCoinLoader: (coinSymbol) => {
|
||||||
const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`);
|
const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`);
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loader.classList.remove('hidden');
|
loader.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideCoinLoader: (coinSymbol) => {
|
hideCoinLoader: (coinSymbol) => {
|
||||||
const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`);
|
const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`);
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loader.classList.add('hidden');
|
loader.classList.add('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCacheStatus: (isCached) => {
|
updateCacheStatus: (isCached) => {
|
||||||
const cacheStatusElement = document.getElementById('cache-status');
|
const cacheStatusElement = document.getElementById('cache-status');
|
||||||
if (cacheStatusElement) {
|
if (cacheStatusElement) {
|
||||||
|
@ -378,15 +377,15 @@ displayCoinData: (coin, data) => {
|
||||||
cacheStatusElement.classList.toggle('text-blue-500', !isCached);
|
cacheStatusElement.classList.toggle('text-blue-500', !isCached);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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);
|
||||||
|
@ -402,7 +401,7 @@ displayCoinData: (coin, data) => {
|
||||||
|
|
||||||
ui.updateLastRefreshedTime();
|
ui.updateLastRefreshedTime();
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePriceChangeContainer: (coin, priceChange) => {
|
updatePriceChangeContainer: (coin, priceChange) => {
|
||||||
const container = document.querySelector(`#${coin.toLowerCase()}-price-change-container`);
|
const container = document.querySelector(`#${coin.toLowerCase()}-price-change-container`);
|
||||||
if (container) {
|
if (container) {
|
||||||
|
@ -411,7 +410,7 @@ displayCoinData: (coin, data) => {
|
||||||
'N/A';
|
'N/A';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateLastRefreshedTime: () => {
|
updateLastRefreshedTime: () => {
|
||||||
const lastRefreshedElement = document.getElementById('last-refreshed-time');
|
const lastRefreshedElement = document.getElementById('last-refreshed-time');
|
||||||
if (lastRefreshedElement && app.lastRefreshedTime) {
|
if (lastRefreshedElement && app.lastRefreshedTime) {
|
||||||
|
@ -419,7 +418,7 @@ displayCoinData: (coin, data) => {
|
||||||
lastRefreshedElement.textContent = `Last Refreshed: ${formattedTime}`;
|
lastRefreshedElement.textContent = `Last Refreshed: ${formattedTime}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
positivePriceChangeHTML: (value) => `
|
positivePriceChangeHTML: (value) => `
|
||||||
<div class="flex flex-wrap items-center py-px px-1 border border-green-500 rounded-full">
|
<div class="flex flex-wrap items-center py-px px-1 border border-green-500 rounded-full">
|
||||||
<svg class="mr-0.5" width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="mr-0.5" width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -428,7 +427,7 @@ displayCoinData: (coin, data) => {
|
||||||
<span class="text-xs text-green-500 font-medium">${value.toFixed(2)}%</span>
|
<span class="text-xs text-green-500 font-medium">${value.toFixed(2)}%</span>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
negativePriceChangeHTML: (value) => `
|
negativePriceChangeHTML: (value) => `
|
||||||
<div class="flex flex-wrap items-center py-px px-1 border border-red-500 rounded-full">
|
<div class="flex flex-wrap items-center py-px px-1 border border-red-500 rounded-full">
|
||||||
<svg class="mr-0.5" width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="mr-0.5" width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -437,7 +436,7 @@ displayCoinData: (coin, data) => {
|
||||||
<span class="text-xs text-red-500 font-medium">${Math.abs(value).toFixed(2)}%</span>
|
<span class="text-xs text-red-500 font-medium">${Math.abs(value).toFixed(2)}%</span>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
formatPrice: (coin, price) => {
|
formatPrice: (coin, price) => {
|
||||||
if (typeof price !== 'number' || isNaN(price)) {
|
if (typeof price !== 'number' || isNaN(price)) {
|
||||||
logger.error(`Invalid price for ${coin}:`, price);
|
logger.error(`Invalid price for ${coin}:`, price);
|
||||||
|
@ -449,7 +448,7 @@ displayCoinData: (coin, data) => {
|
||||||
if (price < 1000) return price.toFixed(2);
|
if (price < 1000) return price.toFixed(2);
|
||||||
return price.toFixed(1);
|
return price.toFixed(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
setActiveContainer: (containerId) => {
|
setActiveContainer: (containerId) => {
|
||||||
const containerIds = ['btc', 'xmr', 'part', 'pivx', 'firo', 'dash', 'ltc', 'doge', 'eth', 'dcr', 'zano', 'wow', 'bch'].map(id => `${id}-container`);
|
const containerIds = ['btc', 'xmr', 'part', 'pivx', 'firo', 'dash', 'ltc', 'doge', 'eth', 'dcr', 'zano', 'wow', 'bch'].map(id => `${id}-container`);
|
||||||
containerIds.forEach(id => {
|
containerIds.forEach(id => {
|
||||||
|
@ -460,7 +459,7 @@ displayCoinData: (coin, data) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
displayErrorMessage: (message) => {
|
displayErrorMessage: (message) => {
|
||||||
const errorOverlay = document.getElementById('error-overlay');
|
const errorOverlay = document.getElementById('error-overlay');
|
||||||
const errorMessage = document.getElementById('error-message');
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
@ -471,7 +470,7 @@ displayCoinData: (coin, data) => {
|
||||||
chartContainer.classList.add('blurred');
|
chartContainer.classList.add('blurred');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideErrorMessage: () => {
|
hideErrorMessage: () => {
|
||||||
const errorOverlay = document.getElementById('error-overlay');
|
const errorOverlay = document.getElementById('error-overlay');
|
||||||
const containersToBlur = document.querySelectorAll('.container-to-blur');
|
const containersToBlur = document.querySelectorAll('.container-to-blur');
|
||||||
|
@ -487,7 +486,7 @@ const chartModule = {
|
||||||
chart: null,
|
chart: null,
|
||||||
currentCoin: 'BTC',
|
currentCoin: 'BTC',
|
||||||
loadStartTime: 0,
|
loadStartTime: 0,
|
||||||
|
|
||||||
cleanup: () => {
|
cleanup: () => {
|
||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
chartModule.chart.destroy();
|
chartModule.chart.destroy();
|
||||||
|
@ -675,7 +674,7 @@ const chartModule = {
|
||||||
plugins: [chartModule.verticalLinePlugin]
|
plugins: [chartModule.verticalLinePlugin]
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
prepareChartData: (coinSymbol, data) => {
|
prepareChartData: (coinSymbol, data) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -689,13 +688,13 @@ 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]);
|
||||||
|
@ -744,6 +743,7 @@ const chartModule = {
|
||||||
y: point.y
|
y: point.y
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -756,11 +756,11 @@ const chartModule = {
|
||||||
|
|
||||||
for (let i = 0; i < 24; i++) {
|
for (let i = 0; i < 24; i++) {
|
||||||
const targetTime = new Date(twentyFourHoursAgo.getTime() + i * 60 * 60 * 1000);
|
const targetTime = new Date(twentyFourHoursAgo.getTime() + i * 60 * 60 * 1000);
|
||||||
const closestDataPoint = data.reduce((prev, curr) =>
|
const closestDataPoint = data.reduce((prev, curr) =>
|
||||||
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
|
||||||
|
@ -774,11 +774,11 @@ 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;
|
||||||
} else {
|
} else {
|
||||||
|
@ -807,7 +807,7 @@ 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';
|
||||||
}
|
}
|
||||||
|
@ -840,25 +840,25 @@ 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) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.classList.remove('hidden');
|
loader.classList.remove('hidden');
|
||||||
chart.classList.add('hidden');
|
chart.classList.add('hidden');
|
||||||
},
|
},
|
||||||
|
|
||||||
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) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.classList.add('hidden');
|
loader.classList.add('hidden');
|
||||||
chart.classList.remove('hidden');
|
chart.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -866,7 +866,7 @@ Chart.register(chartModule.verticalLinePlugin);
|
||||||
|
|
||||||
const volumeToggle = {
|
const volumeToggle = {
|
||||||
isVisible: localStorage.getItem('volumeToggleState') === 'true',
|
isVisible: localStorage.getItem('volumeToggleState') === 'true',
|
||||||
|
|
||||||
cleanup: () => {
|
cleanup: () => {
|
||||||
const toggleButton = document.getElementById('toggle-volume');
|
const toggleButton = document.getElementById('toggle-volume');
|
||||||
if (toggleButton) {
|
if (toggleButton) {
|
||||||
|
@ -876,7 +876,7 @@ const volumeToggle = {
|
||||||
|
|
||||||
init: () => {
|
init: () => {
|
||||||
volumeToggle.cleanup();
|
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);
|
||||||
|
@ -942,7 +942,7 @@ const app = {
|
||||||
app.visibilityCleanup();
|
app.visibilityCleanup();
|
||||||
app.visibilityCleanup = null;
|
app.visibilityCleanup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeToggle.cleanup();
|
volumeToggle.cleanup();
|
||||||
|
|
||||||
app.removeEventListeners();
|
app.removeEventListeners();
|
||||||
|
@ -980,11 +980,11 @@ const app = {
|
||||||
button.removeEventListener('click', window[oldListener]);
|
button.removeEventListener('click', window[oldListener]);
|
||||||
delete window[oldListener];
|
delete window[oldListener];
|
||||||
}
|
}
|
||||||
|
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
const resolution = button.id.split('-')[1];
|
const resolution = button.id.split('-')[1];
|
||||||
const currentCoin = chartModule.currentCoin;
|
const currentCoin = chartModule.currentCoin;
|
||||||
|
|
||||||
if (currentCoin !== 'WOW' || resolution === 'day') {
|
if (currentCoin !== 'WOW' || resolution === 'day') {
|
||||||
config.currentResolution = resolution;
|
config.currentResolution = resolution;
|
||||||
chartModule.updateChart(currentCoin, true);
|
chartModule.updateChart(currentCoin, true);
|
||||||
|
@ -1041,22 +1041,23 @@ const app = {
|
||||||
chartModule.initChart();
|
chartModule.initChart();
|
||||||
chartModule.showChartLoader();
|
chartModule.showChartLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Loading all coin data...');
|
console.log('Loading all coin data...');
|
||||||
await app.loadAllCoinData();
|
await app.loadAllCoinData();
|
||||||
|
|
||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
config.currentResolution = 'day';
|
config.currentResolution = 'day';
|
||||||
await chartModule.updateChart('BTC');
|
await chartModule.updateChart('BTC');
|
||||||
app.updateResolutionButtons('BTC');
|
app.updateResolutionButtons('BTC');
|
||||||
}
|
}
|
||||||
ui.setActiveContainer('btc-container');
|
ui.setActiveContainer('btc-container');
|
||||||
|
|
||||||
app.setupEventListeners();
|
app.setupEventListeners();
|
||||||
app.initializeSelectImages();
|
app.initializeSelectImages();
|
||||||
app.initAutoRefresh();
|
app.initAutoRefresh();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
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();
|
||||||
|
@ -1084,6 +1085,7 @@ const app = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
|
ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1135,19 +1137,19 @@ const app = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshAllButton = document.getElementById('refresh-all');
|
const refreshAllButton = document.getElementById('refresh-all');
|
||||||
if (refreshAllButton) {
|
if (refreshAllButton) {
|
||||||
app.addEventListenerWithCleanup(refreshAllButton, 'click', app.refreshAllData);
|
app.addEventListenerWithCleanup(refreshAllButton, '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', () =>
|
app.addEventListenerWithCleanup(header, '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);
|
app.addEventListenerWithCleanup(closeErrorButton, 'click', ui.hideErrorMessage);
|
||||||
|
@ -1187,6 +1189,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);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1222,16 +1225,16 @@ const app = {
|
||||||
app.isRefreshing = true;
|
app.isRefreshing = true;
|
||||||
ui.showLoader();
|
ui.showLoader();
|
||||||
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();
|
||||||
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 symbol = coin.symbol.toLowerCase();
|
const symbol = coin.symbol.toLowerCase();
|
||||||
const coinData = allCoinData[symbol];
|
const coinData = allCoinData[symbol];
|
||||||
|
@ -1242,16 +1245,17 @@ const app = {
|
||||||
cache.set(cacheKey, coinData);
|
cache.set(cacheKey, coinData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chartModule.currentCoin) {
|
if (chartModule.currentCoin) {
|
||||||
await chartModule.updateChart(chartModule.currentCoin, true);
|
await chartModule.updateChart(chartModule.currentCoin, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.lastRefreshedTime = new Date();
|
app.lastRefreshedTime = new Date();
|
||||||
localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
|
localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
|
||||||
ui.updateLastRefreshedTime();
|
ui.updateLastRefreshedTime();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
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();
|
||||||
|
@ -1268,7 +1272,7 @@ 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) {
|
||||||
|
@ -1277,7 +1281,7 @@ const app = {
|
||||||
|
|
||||||
const updateDisplay = () => {
|
const updateDisplay = () => {
|
||||||
const timeUntilRefresh = Math.max(0, Math.ceil((app.nextRefreshTime - Date.now()) / 1000));
|
const timeUntilRefresh = Math.max(0, Math.ceil((app.nextRefreshTime - Date.now()) / 1000));
|
||||||
|
|
||||||
if (timeUntilRefresh === 0) {
|
if (timeUntilRefresh === 0) {
|
||||||
labelElement.textContent = '';
|
labelElement.textContent = '';
|
||||||
valueElement.textContent = app.refreshTexts.justRefreshed;
|
valueElement.textContent = app.refreshTexts.justRefreshed;
|
||||||
|
@ -1287,12 +1291,12 @@ const app = {
|
||||||
labelElement.textContent = `${app.refreshTexts.label}: `;
|
labelElement.textContent = `${app.refreshTexts.label}: `;
|
||||||
valueElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
valueElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeUntilRefresh > 0) {
|
if (timeUntilRefresh > 0) {
|
||||||
app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay);
|
app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
} else {
|
} else {
|
||||||
labelElement.textContent = '';
|
labelElement.textContent = '';
|
||||||
|
@ -1363,6 +1367,7 @@ const app = {
|
||||||
app.btcPriceUSD = 0;
|
app.btcPriceUSD = 0;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("An error occured:", error.message);
|
||||||
app.btcPriceUSD = 0;
|
app.btcPriceUSD = 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1372,18 +1377,18 @@ const app = {
|
||||||
if (!sortableColumns.includes(columnIndex)) {
|
if (!sortableColumns.includes(columnIndex)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = document.querySelector('table');
|
const table = document.querySelector('table');
|
||||||
if (!table) {
|
if (!table) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = Array.from(table.querySelectorAll('tbody tr'));
|
const rows = Array.from(table.querySelectorAll('tbody tr'));
|
||||||
const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
|
const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
|
||||||
if (!sortIcon) {
|
if (!sortIcon) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
|
const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
|
||||||
sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
|
sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
|
||||||
|
|
||||||
|
@ -1408,7 +1413,7 @@ const app = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (parseTime(bValue) - parseTime(aValue)) * sortOrder;
|
return (parseTime(bValue) - parseTime(aValue)) * sortOrder;
|
||||||
|
|
||||||
case 5: // Rate
|
case 5: // Rate
|
||||||
case 6: // Market +/-
|
case 6: // Market +/-
|
||||||
aValue = getSafeTextContent(a.cells[columnIndex]);
|
aValue = getSafeTextContent(a.cells[columnIndex]);
|
||||||
|
@ -1417,26 +1422,26 @@ const app = {
|
||||||
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');
|
||||||
return (aValue - bValue) * sortOrder;
|
return (aValue - bValue) * sortOrder;
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
aValue = getSafeTextContent(aCell.querySelector('a')) ||
|
aValue = getSafeTextContent(aCell.querySelector('a')) ||
|
||||||
getSafeTextContent(aCell.querySelector('button')) ||
|
getSafeTextContent(aCell.querySelector('button')) ||
|
||||||
getSafeTextContent(aCell);
|
getSafeTextContent(aCell);
|
||||||
bValue = getSafeTextContent(bCell.querySelector('a')) ||
|
bValue = getSafeTextContent(bCell.querySelector('a')) ||
|
||||||
getSafeTextContent(bCell.querySelector('button')) ||
|
getSafeTextContent(bCell.querySelector('button')) ||
|
||||||
getSafeTextContent(bCell);
|
getSafeTextContent(bCell);
|
||||||
|
|
||||||
aValue = aValue.toLowerCase();
|
aValue = aValue.toLowerCase();
|
||||||
bValue = bValue.toLowerCase();
|
bValue = bValue.toLowerCase();
|
||||||
|
|
||||||
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;
|
||||||
return aValue.localeCompare(bValue) * sortOrder;
|
return aValue.localeCompare(bValue) * sortOrder;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
aValue = getSafeTextContent(a.cells[columnIndex]);
|
aValue = getSafeTextContent(a.cells[columnIndex]);
|
||||||
bValue = getSafeTextContent(b.cells[columnIndex]);
|
bValue = getSafeTextContent(b.cells[columnIndex]);
|
||||||
|
@ -1528,7 +1533,7 @@ 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();
|
||||||
|
@ -1541,7 +1546,7 @@ const app = {
|
||||||
app.nextRefreshTime = null;
|
app.nextRefreshTime = null;
|
||||||
localStorage.removeItem('nextRefreshTime');
|
localStorage.removeItem('nextRefreshTime');
|
||||||
}
|
}
|
||||||
|
|
||||||
app.updateAutoRefreshButton();
|
app.updateAutoRefreshButton();
|
||||||
app.updateNextRefreshTime();
|
app.updateNextRefreshTime();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue