import { Chart, Legend, LinearScale, LineController, LineElement, PointElement, TimeSeriesScale, Title, Tooltip } from 'chart.js'; import 'chartjs-adapter-dayjs-3'; import "./style.css" // TODO: This file should be seperated into multiple with better ownership Chart.register( LineElement, PointElement, LineController, LinearScale, TimeSeriesScale, Legend, Title, Tooltip ) let currentRegionSelection = ''; let currentTimeSelection = ''; let currentAggregateSelection = ''; const currentPriceHash = { us: 0, eu: 0, kr: 0, tw: 0 }; let chartData = { us: [], eu: [], kr: [], tw: [] } let chartOptions = { us: { color: 'gold' }, eu: { color: 'red' }, kr: { color: 'white' }, tw: { color: 'pink' } } let chartJsData; let ctx; let tokenChart; function populateChart() { ctx = document.getElementById("token-chart").getContext('2d'); tokenChart = new Chart(ctx, { type: 'line', data: { datasets: [{ borderColor: 'gold', label: currentRegionSelection.toUpperCase() + " WoW Classic Token Price", data: chartJsData, cubicInterpolationMode: 'monotone', pointRadius: 0 }] }, options: { interaction: { intersect: false, mode: "index" }, scales: { x: { type: 'time', ticks: { color: '#a7a4ab', font: { size: 18, } }, }, y: { ticks: { color: '#a7a4ab', font: { size: 18, } } } }, } }); } async function callUpdateURL() { let resp = await fetch("https://data.wowtoken.app/classic/token/current.json"); let data = await resp.json(); updateTokens(data); } function updateTokens(data) { updateRegionalToken('us', data); updateRegionalToken('eu', data); updateRegionalToken('kr', data); updateRegionalToken('tw', data); } function updateRegionalToken(region, data) { if (currentPriceHash[region] !== data['price_data'][region]) { currentPriceHash[region] = data['price_data'][region]; if (region === currentRegionSelection) { formatToken(); if (currentAggregateSelection === 'none') { addDataToChart(region, data); } } } } function addDataToChart(region, data) { if (tokenChart) { const datum = {x: data['current_time'], y: data['price_data'][region]} tokenChart.data.datasets.forEach((dataset) => { dataset.data.push(datum); }) tokenChart.update(); } } async function aggregateFunctionToggle() { // TODO: We should probably make these global or something // so if the need to be updated in the future we can do so easily const smallTimes = ['72h', '168h', '336h']; const longTimes = ['720h', '30d', '2190h', '90d', '1y', '2y', '6m', 'all']; const idsToModify = ['agg_wavg'] if (smallTimes.includes(currentTimeSelection)) { for (const id of idsToModify) { let ele = document.getElementById(id); ele.disabled = true; } } else if (longTimes.includes(currentTimeSelection)) { for (const id of idsToModify) { let ele = document.getElementById(id); ele.disabled = false; } } } function addLoader() { let loader = document.getElementById('loader'); if (!loader) { const blank_div = document.createElement('div'); let loaderNode = blank_div.cloneNode(); loaderNode.id = 'loader'; loaderNode.className = 'lds-ripple'; loaderNode.appendChild(blank_div.cloneNode()); loaderNode.appendChild(blank_div.cloneNode()); let chartNode = document.getElementById('token-chart'); chartNode.before(loaderNode); } } function removeLoader () { let loader = document.getElementById('loader'); if (loader) { loader.remove(); } } function updateRegionPreference(newRegion) { if (newRegion !== currentRegionSelection) { tokenChart.destroy(); addLoader(); currentRegionSelection = newRegion; } formatToken(); pullChartData().then(populateChart); } function updateTimePreference(newTime) { if (newTime !== currentTimeSelection) { tokenChart.destroy(); addLoader(); currentTimeSelection = newTime; aggregateFunctionToggle(); } pullChartData().then(populateChart); } function updateAggregatePreference(newAggregate) { if (newAggregate !== currentAggregateSelection) { tokenChart.destroy(); addLoader(); currentAggregateSelection = newAggregate; } pullChartData().then(populateChart); } function toggleAdvancedSetting() { let element = document.getElementById('advanced-options') if (document.getElementById('enable-advanced').checked) { element.style.display = 'flex'; } else { element.style.display = 'none'; } } function urlBuilder() { let url = "https://data.wowtoken.app/classic/token/history/"; if (currentAggregateSelection !== 'none') { url += `${currentAggregateSelection}/` } url += `${currentRegionSelection}/${currentTimeSelection}.json` return url; } async function pullChartData() { let resp = await fetch(urlBuilder()); let chartData = await resp.json(); let newChartJSData = []; for (let i = 0; i < chartData.length; i++) { let datum = { x: chartData[i]['time'], y: chartData[i]['value'] }; newChartJSData.push(datum); } chartJsData = newChartJSData; removeLoader(); } function formatToken() { document.getElementById("token").innerText = currentPriceHash[currentRegionSelection].toLocaleString(); } // TODO: These maybe able to be collapsed into a single function with params or a lambda function detectRegionQuery(urlSearchParams) { const validRegions = ['us', 'eu', 'tw', 'kr']; if (validRegions.includes(urlSearchParams.get('region').toLowerCase())) { currentRegionSelection = urlSearchParams.get('region').toLowerCase(); let regionDDL = document.getElementById('region'); for (let i = 0; i < regionDDL.options.length; i++) { if (regionDDL.options[i].value === currentRegionSelection) { regionDDL.options[i].selected = true; } } } else { console.warn("An incorrect or malformed region selection was made in the query string"); } } function detectTimeQuery(urlSearchParams) { // In the future, we will allow all the times to be selected, // once I come up with a good reduction algorithm. // For larger time selections, it's currently hardcoded into the backend const validTimes = ['72h', '168h', '336h', '720h', '30d', '2190h', '90d', '1y', '2y', '6m', 'all']; if (validTimes.includes(urlSearchParams.get('time').toLowerCase())) { currentTimeSelection = urlSearchParams.get('time').toLowerCase(); let timeDDL = document.getElementById('time'); for (let i = 0; i < timeDDL.options.length; i++) { if (timeDDL.options[i].value === currentTimeSelection) { timeDDL.options[i].selected = true; } } } else { console.warn("An incorrect or malformed time selection was made in the query string"); } } function detectAggregateQuery(urlSearchParams) { const validOperations = ['none', 'daily_mean', 'weekly_mean']; if (validOperations.includes(urlSearchParams.get('aggregate').toLowerCase())) { currentAggregateSelection = urlSearchParams.get('aggregate').toLowerCase(); let aggregateDDL = document.getElementById('aggregate'); for (let i = 0; i < aggregateDDL.options.length; i++) { if (aggregateDDL.options[i].value === currentAggregateSelection) { aggregateDDL.options[i].selected = true; } } aggregateFunctionToggle(); } else { console.warn("An incorrect or malformed aggregate selection was made in the query string"); } } function detectURLQuery() { const urlSearchParams = new URLSearchParams(window.location.search); if (urlSearchParams.has('region')) { detectRegionQuery(urlSearchParams); } if (urlSearchParams.has('time')) { detectTimeQuery(urlSearchParams); } if (urlSearchParams.has('aggregate')) { detectAggregateQuery(urlSearchParams); } } function buildDeepLinksURL() { let url = "https://classic.wowtoken.app/?" if (currentTimeSelection !== '72h'){ url += `time=${currentTimeSelection}&` } if (currentRegionSelection !== 'us'){ url += `region=${currentRegionSelection}&` } if (currentAggregateSelection !== '' && currentAggregateSelection !== 'none'){ url += `aggregate=${currentAggregateSelection}` } return url } function copyURL() { let toolTip = document.getElementById('urlTooltip'); navigator.clipboard.writeText(buildDeepLinksURL()).then( () => { toolTip.innerHTML= "Copied the URL"; }, () => { toolTip.innerHTML = "Unable to copy URL to clipboard"; } ); } function toolTipMouseOut() { let tooltip = document.getElementById("urlTooltip"); tooltip.innerHTML = "Copy to clipboard"; } function registerEventHandles() { registerCopyHandlers(); registerOptionHandlers(); registerAdvancedHandlers(); } function registerAdvancedHandlers() { document.getElementById('enable-advanced').addEventListener('change', () => { toggleAdvancedSetting(); }) } function registerCopyHandlers() { document.getElementById('copyURLButton').addEventListener('click', function () { copyURL(); }) document.getElementById('copyURLButton').addEventListener('mouseout', function () { toolTipMouseOut(); }) } function registerOptionHandlers() { document.getElementById('region').addEventListener('change', function() { updateRegionPreference(this.value); }); currentRegionSelection = document.getElementById('region').value; document.getElementById('time').addEventListener('change', function() { updateTimePreference(this.value); }); currentTimeSelection = document.getElementById('time').value; document.getElementById('aggregate').addEventListener('change', function () { updateAggregatePreference(this.value); }) currentAggregateSelection = document.getElementById('aggregate').value; } document.addEventListener('DOMContentLoaded', function () { registerEventHandles(); detectURLQuery(); Promise.all([callUpdateURL(), pullChartData()]).then(populateChart) setInterval(callUpdateURL, 60*1000); }, false);