Compare commits

..

No commits in common. "69cd58e48735c675f4fc2055a9c2ae6e346e7f74" and "30f72f91ac6e98c57ed3c098e0459f4b0d878f6f" have entirely different histories.

5 changed files with 103 additions and 225 deletions

View File

@ -3,7 +3,7 @@ version: 0.2
phases:
install:
runtime-versions:
nodejs: 16
nodejs: 14
commands:
- echo Installing dependencies...
- npm install

View File

@ -4,8 +4,7 @@
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production && mkdir -p dist && cp src/robots.txt src/index.html src/style.css src/favicon.ico dist/",
"build-dev": "webpack --mode development && mkdir -p dist && cp src/robots.txt src/index.html src/style.css src/favicon.ico dist/"
"build": "webpack && mkdir -p dist && cp src/robots.txt src/index.html src/style.css src/favicon.ico dist/"
},
"devDependencies": {
"webpack": "^5.73.0",

View File

@ -19,61 +19,32 @@
<div class="lds-ripple" id="loader"><div></div><div></div></div>
<canvas id="token-chart"></canvas>
<div id="option_select">
<p>
<label for="region">Region:</label>
<select name="region" id="region">
<option value="us">US</option>
<option value="eu">EU</option>
<option value="kr">KR</option>
<option value="tw">TW</option>
</select>
</p>
<p>
<label for="time">Time Selection:</label>
<select name="time" id="time">
<option value="72h">3 Days</option>
<option value="168h">7 Days</option>
<option value="336h">14 Days</option>
<option value="720h">1 Month</option>
<option value="90d">3 Months</option>
<option value="6m">6 Months</option>
<option value="1y">1 Year</option>
<option value="2y">2 Years</option>
<option value="all">All Available</option>
</select>
</p>
<p>
<label for="aggregate">Aggregate Function:</label>
<select name="aggregate" id="aggregate">
<option id='agg_none' value="none">None</option>
<option id='agg_dmax' value="daily_max">Daily Maximum</option>
<option id='agg_dmin' value="daily_min">Daily Minimum</option>
<option id='agg_davg' value="daily_mean">Daily Average</option>
<option id='agg_wmax' value="weekly_max" disabled>Weekly Maximum</option>
<option id='agg_wmin' value="weekly_min" disabled>Weekly Minimum</option>
<option id='agg_wavg' value="weekly_mean" disabled>Weekly Average</option>
</select>
</p>
<label for="region">Region:</label>
<select name="region" id="region">
<option value="us">US</option>
<option value="eu">EU</option>
<option value="kr">KR</option>
<option value="tw">TW</option>
</select>
<br />
<label for="time">Time Selection:</label>
<select name="time" id="time">
<option value="72h">3 Days</option>
<option value="168h">7 Days</option>
<option value="336h">14 Days</option>
<option value="720h">1 Month</option>
<option value="90d">3 Months</option>
<option value="6m">6 Months</option>
<option value="1y">1 Year</option>
<option value="2y">2 Years</option>
<option value="all">All Available</option>
</select>
</div>
<details id="about">
<summary>About this Site</summary>
This is a site developed to track the value of the World of Warcraft Token from various
regions over time. I developed it because I wanted a quick and simple way to track the
cost without being advertised to or tracked, and to play around with various "serverless"
technologies. As such, my promise to you is never to use any tracking Javascript, and the
only logging is for debugging purposes of the backend - which does not get IPs.
</details>
<details id="what-is">
<summary>What is the WoW Token</summary>
The World of Warcraft Token is a first-party system developed by Blizzard to allow you
to either spend currency (local denomination or Blizzard Balance) and convert it to gold
in retail World of Warcraft, or use gold to buy game time or Blizzard Balance. To find out
more, visit the support article on Blizzard's website
<a href="https://us.battle.net/support/en/article/31218">here</a>.
</details>
<div id="source">
<p>
<a href="https://blog.emily.sh/wowtoken-app/">Source</a>
<a href="https://github.com/sneaky-emily/wowtoken.app">Source</a>
|
<a href="https://blog.emily.sh/2021/04/developing-a-simple-wow-token-tracker/">About</a>
</p>
</div>
</div>

View File

@ -1,11 +1,11 @@
import {
Chart,
Legend,
LinearScale,
LineController,
LineElement,
PointElement,
LineController,
LinearScale,
TimeSeriesScale,
Legend,
Title,
Tooltip
} from 'chart.js';
@ -24,29 +24,28 @@ Chart.register(
Tooltip
)
let currentRegionSelection = '';
let currentTimeSelection = '';
let currentAggregateSelection = '';
const currentPriceHash = {
let current_region_selection = ''
let current_time_selection = ''
const current_price_hash = {
us: 0,
eu: 0,
kr: 0,
tw: 0
};
let chartJsData;
}
let chart_js_data;
let ctx;
let tokenChart;
let token_chart;
function populateChart() {
ctx = document.getElementById("token-chart").getContext('2d');
tokenChart = new Chart(ctx, {
token_chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
borderColor: 'gold',
label: currentRegionSelection.toUpperCase() + " WoW Token Price",
data: chartJsData,
label: current_region_selection.toUpperCase() + " WoW Token Price",
data: chart_js_data,
cubicInterpolationMode: 'monotone',
pointRadius: 0
}]
@ -80,43 +79,22 @@ function updateTokens(data) {
}
function updateRegionalToken(region, data) {
if (currentPriceHash[region] !== data['price_data'][region]) {
currentPriceHash[region] = data['price_data'][region];
if (region === currentRegionSelection) {
if (current_price_hash[region] !== data['price_data'][region]) {
current_price_hash[region] = data['price_data'][region];
if (region === current_region_selection) {
formatToken();
if (currentAggregateSelection === 'none') {
addDataToChart(region, data);
}
add_data_to_chart(region, data);
}
}
}
function addDataToChart(region, data) {
if (tokenChart) {
function add_data_to_chart(region, data) {
if (token_chart) {
const datum = {x: data['current_time'], y: data['price_data'][region]}
tokenChart.data.datasets.forEach((dataset) => {
token_chart.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_wmax', 'agg_wmin', '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;
}
token_chart.update();
}
}
@ -141,141 +119,97 @@ function removeLoader () {
}
}
function updateRegionPreference(newRegion) {
if (newRegion !== currentRegionSelection) {
tokenChart.destroy();
export function updateRegionPreference(newRegion) {
if (newRegion !== current_region_selection) {
token_chart.destroy();
addLoader();
currentRegionSelection = newRegion;
current_region_selection = newRegion;
}
formatToken();
pullChartData().then(populateChart);
}
function updateTimePreference(newTime) {
if (newTime !== currentTimeSelection) {
tokenChart.destroy();
export function updateTimePreference(newTime) {
if (newTime !== current_time_selection) {
token_chart.destroy();
addLoader();
currentTimeSelection = newTime;
aggregateFunctionToggle();
current_time_selection = newTime;
}
pullChartData().then(populateChart);
}
function updateAggregatePreference(newAggregate) {
if (newAggregate !== currentAggregateSelection) {
tokenChart.destroy();
addLoader();
currentAggregateSelection = newAggregate;
}
pullChartData().then(populateChart);
}
function urlBuilder() {
let url = "https://data.wowtoken.app/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 resp = await fetch("https://data.wowtoken.app/token/history/" + current_region_selection + "/" + current_time_selection + ".json");
let chart_data = await resp.json();
let new_chart_js_data = [];
for (let i = 0; i < chart_data.length; i++) {
let datum = {
x: chartData[i]['time'],
y: chartData[i]['value']
x: chart_data[i]['time'],
y: chart_data[i]['value']
};
newChartJSData.push(datum);
new_chart_js_data.push(datum);
}
chartJsData = newChartJSData;
chart_js_data = new_chart_js_data;
removeLoader();
}
async function updateChartData() {
token_chart.destroy();
pullChartData().then(populateChart);
}
function formatToken() {
$("#token").html(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())) {
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.log("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.log("An incorrect or malformed time selection was made in the query string");
}
}
function detectAggregateQuery(urlSearchParams) {
const validOperations = ['none', 'daily_max', 'daily_min', 'daily_mean', 'weekly_max', 'weekly_min', '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.log("An incorrect or malformed aggregate selection was made in the query string");
}
$("#token").html(current_price_hash[current_region_selection].toLocaleString());
}
function detectURLQuery() {
const urlSearchParams = new URLSearchParams(window.location.search);
const urlSearchParams = new URLSearchParams(window.location.search)
const allowedRegions = ['us', 'eu', 'tw', 'kr']
if (urlSearchParams.has('region')) {
detectRegionQuery(urlSearchParams);
if (allowedRegions.includes(urlSearchParams.get('region').toLowerCase())) {
current_region_selection = urlSearchParams.get('region').toLowerCase()
let region_ddl = document.getElementById('region')
for (let i = 0; i < region_ddl.options.length; i++){
if (region_ddl.options[i].value === current_region_selection) {
region_ddl.options[i].selected = true;
}
}
} else {
console.log("An incorrect or malformed region selection was made in the query string")
}
}
// 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', '90d', '1y', '2y', '6m', 'all'];
if (urlSearchParams.has('time')) {
detectTimeQuery(urlSearchParams);
}
if (urlSearchParams.has('aggregate')) {
detectAggregateQuery(urlSearchParams);
if (validTimes.includes(urlSearchParams.get('time').toLowerCase())) {
current_time_selection = urlSearchParams.get('time').toLowerCase();
let time_ddl = document.getElementById('time')
for (let i = 0; i < time_ddl.options.length; i++){
if (time_ddl.options[i].value === current_time_selection) {
time_ddl.options[i].selected = true;
}
}
} else {
console.log("An incorrect or malformed time selection was made in the query string");
}
}
}
$(document).ready(function() {
document.getElementById('region').addEventListener('change', function() {
updateRegionPreference(this.value);
});
currentRegionSelection = document.getElementById('region').value;
current_region_selection = 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;
current_time_selection = document.getElementById('time').value;
detectURLQuery();
Promise.all([callUpdateURL(), pullChartData()]).then(populateChart)
callUpdateURL();
setInterval(callUpdateURL, 60*1000);
pullChartData().then(populateChart);
});

View File

@ -181,7 +181,7 @@ html {
margin: 1em;
}
/*body {
}*/
code {
background-color: #073642;
@ -309,7 +309,7 @@ h6 {
max-width: 85%;
border: 1pt solid #586e75;
padding: 1em;
}
}
.flex-container > div {
flex: 100%;
@ -324,32 +324,6 @@ p {
line-height: 3em;
}
details {
background-color: #073642;
border: 1px solid #aaa;
border-radius: 4px;
padding: 0.5em 0.5em 0;
font-size: 17px;
line-height: 1.5em;
margin: .5em 5vw;
}
details > summary {
color: #aaaaaa;
cursor: pointer;
font-weight: bold;
margin: -0.5em -0.5em 0;
padding: 0.5em;
}
details[open] {
padding: 0.5em;
}
details[open] summary {
border-bottom: 1px solid #aaa;
margin-bottom: 0.5em;
}
#option_select {
font-size: 20px;
@ -378,12 +352,12 @@ details[open] summary {
flex-direction: column;
}
.lds-ripple {
position: relative;
align-self: center;
width: 80px;
height: 80px;
margin-bottom: -80px;
}
.lds-ripple div {
position: absolute;