diff --git a/.gitignore b/.gitignore
index ef8f3b1..a6600ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
-# Include your project-specific ignores in this file
-# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
-# Useful .gitignore templates: https://github.com/github/gitignore
+
+/.idea/
+/node_modules/
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..1a2fb33
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..df7825d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 2f13639..fc7e992 100644
--- a/index.html
+++ b/index.html
@@ -25,8 +25,6 @@
your browser to improve your experience and security.
-
-
diff --git a/js/main.js b/js/main.js
index b02e3a6..7ce6535 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,584 +1,507 @@
// Setup
-subscriptions = []
-
-currencies = new Map()
-currencies.set("$", 6.78)
-currencies.set("€", 7.47)
-currencies.set("DKK", 1)
-
+let subscriptions = [];
+const currencies = new Map();
+currencies.set("$", 6.78);
+currencies.set("€", 7.47);
+currencies.set("DKK", 1);
// The name of the data in localStorage
-localDataName = "subscriptionInformation"
-localSortPreferenceName = "sortPreference"
-localHasChangedOrder = "hasChangedOrder"
-
+const localDataName = "subscriptionInformation";
+let localSortPreferenceName = "sortPreference";
// Initial scroll distance to bottom of the page.
-let scrollDistance = getDocHeight()
-
+let scrollDistance = getDocHeight();
// Function that top/bottom button calls. Scrolls to top if user has scrolled 50% down, bottom otherwise.
let scrollToTopOrBottom = () => {
- window.scrollTo({
- top: scrollDistance,
- left: 0,
- behavior: 'smooth'
- })
-}
-
+ window.scrollTo({
+ top: scrollDistance,
+ left: 0,
+ behavior: 'smooth'
+ });
+};
// Gets the height of the page.
function getDocHeight() {
- let D = document;
- return Math.max(
- D.body.scrollHeight, D.documentElement.scrollHeight,
- D.body.offsetHeight, D.documentElement.offsetHeight,
- D.body.clientHeight, D.documentElement.clientHeight
- )
+ let D = document;
+ return Math.max(D.body.scrollHeight, D.documentElement.scrollHeight, D.body.offsetHeight, D.documentElement.offsetHeight, D.body.clientHeight, D.documentElement.clientHeight);
}
-
// Keep track of the window height.
let winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
window.onresize = () => {
- winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
-}
-
-// Calculate whether the button should say top or bottom
-window.onscroll = () => {
- let docHeight = getDocHeight()
- let trackLength = docHeight - winheight
-
- let scrollTop = window.pageYOffset || (document.documentElement || document.body.parentNode || document.body).scrollTop
- let pctScrolled = Math.floor(scrollTop/trackLength * 100) // gets percentage scrolled (ie: 80 or NaN if tracklength == 0)
-
- if (pctScrolled > 50){
- document.querySelector('#myBtn').innerHTML = "Top";
- scrollDistance = 0
- }
- else{
- document.querySelector('#myBtn').innerHTML = "Bottom"
- scrollDistance = trackLength
- }
-}
-
+ winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
+};
// Returns true if valid subscription, false if not.
+let isValidNewSubscription = (subscription) => {
+ // Title of subscription must be unique.
+ if (subscriptions.map((sub) => sub.title).includes(subscription.title)) {
+ alert("Subscription name not unique");
+ return false;
+ }
+ return isValidSubscription(subscription);
+};
let isValidSubscription = (subscription) => {
- if (isNaN(subscription.amount)) {
- alert("Amount is not a number")
- return false
- }
- if (!([...currencies.keys()].includes(subscription.currency))) {
- alert("Currency is not a valid currency")
- return false
- }
- if (isNaN(subscription.months)) {
- alert("Months are not a number")
- return false
- }
-
- // Title of subscription must be unique.
- if (subscriptions.map((sub) => sub.title).includes(subscription.title)) {
- alert("Subscription name not unique")
- return false
- }
-
- return true
-}
-
+ if (isNaN(subscription.price)) {
+ alert("Amount is not a number");
+ return false;
+ }
+ if (!([...currencies.keys()].includes(subscription.currency))) {
+ alert("Currency is not a valid currency");
+ return false;
+ }
+ if (isNaN(subscription.months)) {
+ alert("Months are not a number");
+ return false;
+ }
+ return true;
+};
+let createNewSubscription = (title, price, currency, months, notes) => {
+ const newSub = {
+ changed: false,
+ currency: currency,
+ indkk: 0,
+ monthly: 0,
+ months: months,
+ notes: notes,
+ percentOfWhole: null,
+ price: price,
+ title: title
+ };
+ newSub.monthly = newSub.price / newSub.months;
+ newSub.indkk = newSub.monthly * currencies.get(newSub.currency);
+ return newSub;
+};
// Create a new subscription
let newSubscription = () => {
- // Open the popup
- Swal.fire({
- title: 'Insert subscription information',
- html:
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '',
- focusConfirm: false,
- preConfirm: () => {
- // Create a new temporary subscription
- let newSub = {
- title: document.getElementById('swal-input1').value,
- amount: document.getElementById('swal-input2').value,
- currency: document.getElementById('swal-input3').value,
- months: document.getElementById('swal-input4').value,
- notes: document.getElementById('swal-input5').value,
- changed: false
- }
-
- // Do some form validation
- let valid = isValidSubscription(newSub)
-
- // Update the table if form was validated successfully
- if (valid){
- subscriptions.push(newSub)
- updateTable()
- }
- }
- })
-}
-
+ // Open the popup
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert subscription information',
+ html: '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '',
+ focusConfirm: false,
+ preConfirm: () => {
+ // Create a new temporary subscription
+ const title = document.getElementById('swal-input1').value;
+ const price = parseFloat(document.getElementById('swal-input2').value);
+ const currency = document.getElementById('swal-input3').value;
+ const months = parseInt(document.getElementById('swal-input4').value);
+ const notes = document.getElementById('swal-input5').value;
+ const newSub = createNewSubscription(title, price, currency, months, notes);
+ // Do some form validation
+ let valid = isValidNewSubscription(newSub);
+ // Update the table if form was validated successfully
+ if (valid) {
+ subscriptions.push(newSub);
+ updateTable();
+ }
+ }
+ });
+};
// Edit a subscription
let editSubscription = (btn) => {
- // Find out which subscription we're editing
- let currentSubscription = getSubscriptionFromButton(btn)
-
- // Open the popup, and fill in the current subscription information in the form
- Swal.fire({
- title: 'Insert subscription information',
- html:
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '' +
- ' ' +
- '',
- focusConfirm: false,
- cancelButtonColor: '#d33',
- showCancelButton: true,
- preConfirm: () => {
- // Create a new temporary subscription
- let newSub = {
- title: document.getElementById('swal-input1').value,
- amount: document.getElementById('swal-input2').value,
- currency: document.getElementById('swal-input3').value,
- months: document.getElementById('swal-input4').value,
- notes: document.getElementById('swal-input5').value,
- changed: false
- }
-
- let valid = isValidSubscription(newSub)
- if (valid){
- // Update the table if form was validated successfully
- deleteSubscription(currentSubscription)
- subscriptions.push(newSub)
- updateTable()
- }
- }
- })
-}
-
+ // Find out which subscription we're editing
+ let currentSubscription = getSubscriptionFromButton(btn);
+ // Open the popup, and fill in the current subscription information in the form
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert subscription information',
+ html: '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '',
+ focusConfirm: false,
+ cancelButtonColor: '#d33',
+ showCancelButton: true,
+ preConfirm: () => {
+ // Create a new temporary subscription
+ const title = document.getElementById('swal-input1').value;
+ const price = parseFloat(document.getElementById('swal-input2').value);
+ const currency = document.getElementById('swal-input3').value;
+ const months = parseInt(document.getElementById('swal-input4').value);
+ const notes = document.getElementById('swal-input5').value;
+ const editedSub = createNewSubscription(title, price, currency, months, notes);
+ let valid = isValidSubscription(editedSub);
+ console.log(valid);
+ if (valid) {
+ console.log(editedSub);
+ // Update the table if form was validated successfully
+ deleteSubscription(currentSubscription);
+ subscriptions.push(editedSub);
+ updateTable();
+ }
+ }
+ });
+};
// Add a new currency. Called by the add currency button
let addCurrency = () => {
- // Fire popup
- Swal.fire({
- title: 'Insert currency information',
- html:
- '' +
- '',
- focusConfirm: false,
- preConfirm: () => {
- // Register the new possible currency
- let newCurrency = {
- symbol: document.getElementById('swal-input1').value,
- rate: document.getElementById('swal-input2').value,
- }
-
- // Check that the rate is a number
- if (isNaN(newCurrency.rate)) {
- alert("DKK exchange rate is now a number")
- return
- }
-
- // Update with the new currency, and refresh the table
- currencies.set(newCurrency.symbol, newCurrency.rate)
- updateTable()
- }
- })
-}
-
+ // Fire popup
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert currency information',
+ html: '' +
+ '',
+ focusConfirm: false,
+ preConfirm: () => {
+ // Register the new possible currency
+ let newCurrency = {
+ symbol: document.getElementById('swal-input1').value,
+ rate: parseFloat(document.getElementById('swal-input2').value),
+ };
+ // Check that the rate is a number
+ if (isNaN(newCurrency.rate)) {
+ alert("DKK exchange rate is now a number");
+ return;
+ }
+ // Update with the new currency, and refresh the table
+ currencies.set(newCurrency.symbol, newCurrency.rate);
+ updateTable();
+ }
+ });
+};
// Delete all subscriptions.
let Reset = () => {
- Swal.fire({
- title: 'Are you sure?',
- text: "You won't be able to revert this!",
- icon: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#3085d6',
- cancelButtonColor: '#d33',
- confirmButtonText: 'Yes, delete it!'
- }).then((result) => {
- if (result.value) {
- subscriptions = []
- updateTable()
- Swal.fire(
- 'Reset!',
- 'Your subscriptions have been reset.',
- 'success'
- )
- }
- })
-}
-
-
+ // @ts-ignore
+ Swal.fire({
+ title: 'Are you sure?',
+ text: "You won't be able to revert this!",
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Yes, delete it!'
+ }).then((result) => {
+ if (result.value) {
+ subscriptions.length = 0;
+ updateTable();
+ // @ts-ignore
+ Swal.fire('Reset!', 'Your subscriptions have been reset.', 'success');
+ }
+ });
+};
// Method for updating the table with new information.
let updateTable = () => {
- // Create a new replacement tbody
- let newTbody = document.createElement('tbody');
- newTbody.classList.add("tbody")
-
- // Sort by users preference
-
-
- // Add rows from data in memory.
- populateTable(newTbody)
-
- // Replace the old tbody with the new cleared one
- let oldTbody = document.querySelector('.tbody')
- oldTbody.parentNode.replaceChild(newTbody, oldTbody)
-
- // Save the new information locally.
- setStored(localDataName, JSON.stringify(getSaveInfo()))
-}
-
+ // Create a new replacement tbody
+ let newTbody = document.createElement('tbody');
+ newTbody.classList.add("tbody");
+ // Sort by users preference
+ // Add rows from data in memory.
+ populateTable(newTbody);
+ // Replace the old tbody with the new cleared one
+ let oldTbody = document.querySelector('.tbody');
+ oldTbody.parentNode.replaceChild(newTbody, oldTbody);
+ // Save the new information locally.
+ setStored(localDataName, JSON.stringify(getSaveInfo()));
+};
// Populate a tbody with subscriptions from the subscriptions array.
let populateTable = (tbody) => {
- // Store the total cost of all subscriptions
- total = 0
-
- // calculate the monthly cost in dkk of each item, and add it to the running total
- for (elem of subscriptions) {
- elem.monthly = elem.amount / elem.months
- elem.indkk = elem.monthly * currencies.get(elem.currency)
- total += elem.indkk
- }
-
- // Update tablefoot with total monthly cost and amount of subscriptions.
- document.querySelector('#total').innerHTML = total.toFixed(2)
- document.querySelector('#amountOfSubscriptions').innerHTML = subscriptions.length
-
- // Begin to create table rows
- for (let i = 0; i < subscriptions.length; i++) {
- // Select a subscription on index i, and create a new empty row
- let elem = subscriptions[i]
- var tr = document.createElement('tr');
-
- // Calculate the subscriptions percentage-wise cost of the monthly total.
- elem.percentOfWhole = elem.indkk / total * 100;
-
- // If this element has been changed (ie. created, moved), mark it for the user.
- if (elem.changed) {
- tr.classList = "is-selected"
- elem.changed = false
+ // Store the total cost of all subscriptions
+ let total = subscriptions.map((s) => s.monthly).reduce((acc, sub) => acc + sub);
+ // Update tablefoot with total monthly cost and amount of subscriptions.
+ document.querySelector('#total').innerHTML = total.toFixed(2);
+ document.querySelector('#amountOfSubscriptions').innerHTML = subscriptions.length.toString();
+ // Begin to create table rows
+ for (let i = 0; i < subscriptions.length; i++) {
+ // Select a subscription on index i, and create a new empty row
+ let elem = subscriptions[i];
+ let tr = document.createElement('tr');
+ // Calculate the subscriptions percentage-wise cost of the monthly total.
+ elem.percentOfWhole = elem.indkk / total * 100;
+ // If this element has been changed (ie. created, moved), mark it for the user.
+ if (elem.changed) {
+ tr.classList.add("is-selected");
+ elem.changed = false;
+ }
+ // Create the row.
+ tr.innerHTML = '
' + elem.title + '
' +
+ '
' + elem.monthly.toFixed(2) + '
' +
+ '
' + elem.currency + '
' +
+ '
' + elem.indkk.toFixed(2) + '
' +
+ '
' + elem.percentOfWhole.toFixed(2) + '%
' +
+ '
' + '' + '
' +
+ '
' + '' + '
' +
+ '
' + `` + '
';
+ // Append the new row
+ tbody.appendChild(tr);
}
-
- // Create the row.
- tr.innerHTML = '
' + elem.title + '
' +
- '
' + elem.monthly.toFixed(2) + '
' +
- '
' + elem.currency + '
' +
- '
' + elem.indkk.toFixed(2) + '
' +
- '
' + elem.percentOfWhole.toFixed(2) + '%
' +
- '
' + '' + '
' +
- '
' + '' + '
' +
- '
' + `` + `` + `` + '
';
-
- // Append the new row
- tbody.appendChild(tr);
- }
-}
-
+};
// Create a pie-chart with the cost of each subscription
let PieChart = () => {
-
- // Get data ready for CanvasJS.
- let data = []
- for (subscription of subscriptions) {
- console.log(subscription)
- data.push({y: subscription.percentOfWhole, label: subscription.title})
- }
-
- // Create the chart with CanvasJS.
- let chart = new CanvasJS.Chart("chartContainer", {
- animationEnabled: true,
- title: {
- text: "Monthly Expenses"
- },
- data: [{
- type: "pie",
- startAngle: 240,
- yValueFormatString: "##0.00\"%\"",
- indexLabel: "{label} {y}",
- dataPoints: data
- }]
- });
-
- // Lastly, render it.
- chart.render()
-}
-
+ // Get data ready for CanvasJS.
+ let data = [];
+ for (let subscription of subscriptions) {
+ console.log(subscription);
+ data.push({ y: subscription.percentOfWhole, label: subscription.title });
+ }
+ // Create the chart with CanvasJS.
+ // @ts-ignore
+ let chart = new CanvasJS.Chart("chartContainer", {
+ animationEnabled: true,
+ title: {
+ text: "Monthly Expenses"
+ },
+ data: [{
+ type: "pie",
+ startAngle: 240,
+ yValueFormatString: "##0.00\"%\"",
+ indexLabel: "{label} {y}",
+ dataPoints: data
+ }]
+ });
+ // Lastly, render it.
+ chart.render();
+};
// Ask the user what to sort by, and then sorts the array.
let Sort = async () => {
-
- // The input options for SWAL2
- const inputOptions = new Promise((resolve) => {
- resolve({
- 'Lex': 'Lexi.',
- 'Price': 'Price'
- })
- })
-
- // Fire the popup
- const {value: sortBy} = await Swal.fire({
- title: 'Sort by what',
- input: 'radio',
- inputOptions: inputOptions,
- inputValidator: (value) => {
- if (!value) {
- return 'You need to choose something!'
- }
+ // The input options for SWAL2
+ const inputOptions = new Promise((resolve) => {
+ resolve({
+ 'Lex': 'Lexi.',
+ 'Price': 'Price'
+ });
+ });
+ // Fire the popup
+ // @ts-ignore
+ const { value: sortBy } = await Swal.fire({
+ title: 'Sort by what',
+ input: 'radio',
+ inputOptions: inputOptions,
+ inputValidator: (value) => {
+ if (!value) {
+ return 'You need to choose something!';
+ }
+ }
+ });
+ // Sort the array, and save the sorting preference
+ if (sortBy) {
+ if (sortBy == "Lex") {
+ setStored(localSortPreferenceName, "Lex");
+ sortSubscriptionsByName();
+ updateTable();
+ }
+ else if (sortBy == "Price") {
+ setStored(localSortPreferenceName, "Price");
+ sortSubscriptionsByPrice();
+ updateTable();
+ }
}
- })
-
- // Sort the array, and save the sorting preference
- if (sortBy) {
- if (sortBy == "Lex") {
- setStored(localSortPreferenceName, "Lex")
- sortSubscriptionsByName()
- updateTable()
- } else if (sortBy == "Price") {
- setStored(localSortPreferenceName, "Price")
- sortSubscriptionsByPrice()
- updateTable()
- }
- }
-}
-
+};
// different sorters for subscriptions array
let sortSubscriptionsByPrice = () => {
- subscriptions.sort((a, b) => {
- if (a.indkk > b.indkk) {
- return -1;
- }
- if (b.indkk > a.indkk) {
- return 1;
- }
- return 0;
- })
-}
+ subscriptions.sort((a, b) => {
+ if (a.indkk > b.indkk) {
+ return -1;
+ }
+ if (b.indkk > a.indkk) {
+ return 1;
+ }
+ return 0;
+ });
+};
let sortSubscriptionsByName = () => {
- subscriptions.sort((a, b) => {
- if (a.title > b.title) {
- return 1;
- }
- if (b.title > a.title) {
- return -1;
- }
- return 0;
- })
-}
-
-
+ subscriptions.sort((a, b) => {
+ if (a.title > b.title) {
+ return 1;
+ }
+ if (b.title > a.title) {
+ return -1;
+ }
+ return 0;
+ });
+};
// Gets more information on a subscription, and displays it in a popup
let getInfoOfSubscription = (btn) => {
- let currentSub = getSubscriptionFromButton(btn)
- Swal.fire(
- currentSub.title,
- `${currentSub.amount}${currentSub.currency} over ${currentSub.months} months, which equates to
+ let currentSub = getSubscriptionFromButton(btn);
+ // @ts-ignore
+ Swal.fire(currentSub.title, `${currentSub.price}${currentSub.currency} over ${currentSub.months} months, which equates to
${currentSub.monthly}${currentSub.currency} monthly, or about ${currentSub.indkk} DKK ` +
- // Display notes only if there are some.
- (currentSub.notes === undefined ? "" : (
- `
+ // Display notes only if there are some.
+ (currentSub.notes === undefined ? "" : (`
Notes:
- `)),
- 'question'
- )
-}
-
+ `)), 'question');
+};
// Since title is unique, this finds the title of the row from which a button was pressed, and returns that subscription from the subscription array
let getSubscriptionFromButton = (button) => {
- let tr = button.parentNode.parentNode
- let title = tr.firstChild.textContent
- return subscriptions.filter(s => s.title == title)[0]
-}
-
-// Moves a row by some direction (+x, -x)
-let moveRow = (btn, direction) => {
-
- // Find the subscriptions current index
- let currentSub = getSubscriptionFromButton(btn)
- let indexOfCurrentRow = subscriptions.indexOf(currentSub)
-
- // If trying to move a subscription out of bounds
- if (indexOfCurrentRow + direction < 0 || indexOfCurrentRow + direction >= subscriptions.length) {
- Swal.fire({
- icon: 'error',
- title: 'Oops...',
- text: 'Cant move this object ' + (direction == -1 ? "up" : "down"),
- })
- } else {
- // Move the subscription
- subscriptions[indexOfCurrentRow] = subscriptions[indexOfCurrentRow + direction]
- subscriptions[indexOfCurrentRow + direction] = currentSub
- currentSub.changed = true
- updateTable()
-
- // Store that the user has a custom order
- setStored(localHasChangedOrder, true)
- }
-}
-
-
+ let tr = button.parentNode.parentNode;
+ let title = tr.firstChild.textContent;
+ return subscriptions.filter(s => s.title == title)[0];
+};
let askToDeleteRow = (btn) => {
- Swal.fire({
- title: 'Are you sure?',
- text: "You won't be able to revert this!",
- icon: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#3085d6',
- cancelButtonColor: '#d33',
- confirmButtonText: 'Yes, delete it!'
- }).then((result) => {
- if (result.value) {
- deleteSubscription(getSubscriptionFromButton(btn))
- Swal.fire(
- 'Deleted!',
- 'Your file has been deleted.',
- 'success'
- )
- }
- })
-}
-
+ // @ts-ignore
+ Swal.fire({
+ title: 'Are you sure?',
+ text: "You won't be able to revert this!",
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Yes, delete it!'
+ }).then((result) => {
+ if (result.value) {
+ deleteSubscription(getSubscriptionFromButton(btn));
+ // @ts-ignore
+ Swal.fire('Deleted!', 'Your file has been deleted.', 'success');
+ }
+ });
+};
let deleteSubscription = (subscription) => {
- subscriptions = subscriptions.filter(s => s.title != subscription.title)
- updateTable()
-}
-
+ const index = subscriptions.map(s => s.title).indexOf(subscription.title);
+ subscriptions.splice(index, 1);
+ updateTable();
+};
// Make subscriptions and currency ready to be stored as JSON
let getSaveInfo = () => {
- return {subscriptions: subscriptions, keys: [...currencies.keys()], values: [...currencies.values()]}
-}
-
+ return { version: 1, subscriptions: subscriptions, keys: [...currencies.keys()], values: [...currencies.values()] };
+};
// Parse a save object, and use it as the current info.
-let loadSaveInfo = (loadedSave) => {
- subscriptions = loadedSave.subscriptions
-
- currencies.clear()
- for (let i = 0; i < loadedSave.keys.length; i++) {
- currencies.set(loadedSave.keys[i], loadedSave.values[i])
- }
-}
-
+let loadSaveInfo = (loadedSave, oldLoadedSave) => {
+ let actualSaveToUse = loadedSave;
+ if (oldLoadedSave !== null) {
+ // Old data
+ console.log("loading legacy data...");
+ const oldData = oldLoadedSave;
+ let newData = { keys: [], subscriptions: [], values: [], version: 0 };
+ newData.keys = oldData.keys;
+ newData.version = 1;
+ newData.values = oldData.values;
+ for (let sub of oldLoadedSave.subscriptions) {
+ const newSub = createNewSubscription(sub.title, sub.amount, sub.currency, sub.months, sub.notes);
+ newData.subscriptions.push(newSub);
+ }
+ actualSaveToUse = newData;
+ }
+ subscriptions = actualSaveToUse.subscriptions;
+ currencies.clear();
+ for (let i = 0; i < actualSaveToUse.keys.length; i++) {
+ currencies.set(actualSaveToUse.keys[i], actualSaveToUse.values[i]);
+ }
+};
// Save a local JSON version of the current state.
let saveToFile = () => {
- var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(getSaveInfo()));
- var downloadAnchorNode = document.createElement('a');
- downloadAnchorNode.setAttribute("href", dataStr);
- downloadAnchorNode.setAttribute("download", "save" + ".json");
- document.body.appendChild(downloadAnchorNode); // required for firefox
- downloadAnchorNode.click();
- downloadAnchorNode.remove();
-}
-
+ var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(getSaveInfo()));
+ var downloadAnchorNode = document.createElement('a');
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", "save" + ".json");
+ document.body.appendChild(downloadAnchorNode); // required for firefox
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+};
// Load a local JSON version of some state.
let load = () => {
- var input, file, fr;
-
- if (typeof window.FileReader !== 'function') {
- alert("The file API isn't supported on this browser yet.");
- return;
- }
-
- input = document.getElementById('fileinput');
- if (!input) {
- alert("Um, couldn't find the fileinput element.");
- } else if (!input.files) {
- alert("This browser doesn't seem to support the `files` property of file inputs.");
- } else if (!input.files[0]) {
- alert("Please select a file before clicking 'Load'");
- } else {
- file = input.files[0];
- fr = new FileReader();
- fr.onload = receivedText;
- fr.readAsText(file);
- }
-
- function receivedText(e) {
- let lines = e.target.result;
- loadedSave = JSON.parse(lines);
- loadSaveInfo(loadedSave)
-
- updateTable()
- }
-}
-
+ var input, file, fr;
+ if (typeof window.FileReader !== 'function') {
+ alert("The file API isn't supported on this browser yet.");
+ return;
+ }
+ input = document.getElementById('fileinput');
+ if (!input) {
+ alert("Um, couldn't find the fileinput element.");
+ }
+ else if (!input.files) {
+ alert("This browser doesn't seem to support the `files` property of file inputs.");
+ }
+ else if (!input.files[0]) {
+ alert("Please select a file before clicking 'Load'");
+ }
+ else {
+ file = input.files[0];
+ fr = new FileReader();
+ fr.onload = receivedText;
+ fr.readAsText(file);
+ }
+ function receivedText(e) {
+ let lines = e.target.result;
+ let loadedSave = JSON.parse(lines);
+ if (loadedSave.version == null) {
+ loadSaveInfo(null, loadedSave);
+ }
+ else {
+ loadSaveInfo(loadedSave, null);
+ }
+ updateTable();
+ }
+};
// Retrieve the key value pair vname, vvalue
function getStored(vname) {
- if (typeof (Storage) !== "undefined") {
- return localStorage.getItem(vname)
- } else {
- return getCookie(vname)
- }
+ if (typeof (Storage) !== "undefined") {
+ return localStorage.getItem(vname);
+ }
+ else {
+ return getCookie(vname);
+ }
}
-
// Store the key value pair vname, vvalue
function setStored(vname, vvalue) {
- if (typeof (Storage) !== "undefined") {
- return localStorage.setItem(vname, vvalue)
- } else {
- return setCookie(vname, vvalue)
- }
+ if (typeof (Storage) !== "undefined") {
+ return localStorage.setItem(vname, vvalue);
+ }
+ else {
+ return setCookie(vname, vvalue, 1000);
+ }
}
-
-
// Fallback if browser doesn't support HTML5 localstorage
function setCookie(cname, cvalue, exdays) {
- var d = new Date();
- d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
- var expires = "expires=" + d.toUTCString();
- document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
+ var d = new Date();
+ d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
+ var expires = "expires=" + d.toUTCString();
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
-
function getCookie(cname) {
- var name = cname + "=";
- var decodedCookie = decodeURIComponent(document.cookie);
- var ca = decodedCookie.split(';');
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == ' ') {
- c = c.substring(1);
+ var name = cname + "=";
+ var decodedCookie = decodeURIComponent(document.cookie);
+ var ca = decodedCookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
}
- if (c.indexOf(name) == 0) {
- return c.substring(name.length, c.length);
- }
- }
- return "";
+ return "";
}
-
-
// Try to load data from local storage. If this fails, there's probably not any data to retrieve.
try {
- loadSaveInfo(JSON.parse(getStored(localDataName)))
-
- // Find out if we want to sort the array of the users data.
- if (!getStored(localHasChangedOrder)){
- let userSortPref = getStored(localSortPreferenceName)
- if (userSortPref === undefined){
- sortSubscriptionsByName()
+ let data = JSON.parse(getStored(localDataName));
+ if (data.version == null) {
+ loadSaveInfo(null, data);
}
- else{
- if (userSortPref == "Price"){
- sortSubscriptionsByPrice()
- }
- else{
- sortSubscriptionsByName()
- }
+ else {
+ loadSaveInfo(data, null);
}
- }
- updateTable()
-} catch (e) {
- console.log("Didn't find any locally stored data")
- console.log(e)
+ // Find out if we want to sort the array of the users data.
+ let userSortPref = getStored(localSortPreferenceName);
+ if (userSortPref === undefined) {
+ sortSubscriptionsByName();
+ }
+ else {
+ if (userSortPref == "Price") {
+ sortSubscriptionsByPrice();
+ }
+ else {
+ sortSubscriptionsByName();
+ }
+ }
+ updateTable();
}
+catch (e) {
+ console.log("Didn't find any locally stored data");
+ console.log(e);
+}
+//# sourceMappingURL=main.js.map
\ No newline at end of file
diff --git a/js/main.js.map b/js/main.js.map
new file mode 100644
index 0000000..1d7c961
--- /dev/null
+++ b/js/main.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/js/main.ts b/js/main.ts
new file mode 100644
index 0000000..a0a9acd
--- /dev/null
+++ b/js/main.ts
@@ -0,0 +1,640 @@
+interface subscription{
+ title: String,
+ price: number,
+ currency: String,
+ months: number,
+ notes: String,
+ monthly: number,
+ indkk: number,
+ percentOfWhole: number,
+ changed: boolean
+}
+
+interface oldSubscription{
+ title: String,
+ amount: number,
+ currency: String,
+ months: number,
+ notes: String,
+ monthly: number,
+ indkk: number,
+ percentOfWhole: number,
+ changed: boolean
+}
+
+interface oldSave{
+ subscriptions: oldSubscription[],
+ keys: string[], //Currencies symbols
+ values: number[] // Currencies values
+}
+
+interface save{
+ version: number,
+ subscriptions: subscription[],
+ keys: String[], //Currencies symbols
+ values: number[] // Currencies values
+}
+
+// Setup
+let subscriptions: subscription[] = [];
+
+const currencies:Map = new Map()
+currencies.set("$", 6.78)
+currencies.set("€", 7.47)
+currencies.set("DKK", 1)
+
+// The name of the data in localStorage
+const localDataName = "subscriptionInformation"
+let localSortPreferenceName = "sortPreference"
+
+// Initial scroll distance to bottom of the page.
+let scrollDistance = getDocHeight()
+
+// Function that top/bottom button calls. Scrolls to top if user has scrolled 50% down, bottom otherwise.
+let scrollToTopOrBottom = () => {
+ window.scrollTo({
+ top: scrollDistance,
+ left: 0,
+ behavior: 'smooth'
+ })
+}
+
+// Gets the height of the page.
+function getDocHeight() {
+ let D = document;
+ return Math.max(
+ D.body.scrollHeight, D.documentElement.scrollHeight,
+ D.body.offsetHeight, D.documentElement.offsetHeight,
+ D.body.clientHeight, D.documentElement.clientHeight
+ )
+}
+
+// Keep track of the window height.
+let winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
+window.onresize = () => {
+ winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
+}
+
+// Returns true if valid subscription, false if not.
+let isValidNewSubscription = (subscription): Boolean => {
+
+ // Title of subscription must be unique.
+ if (subscriptions.map((sub) => sub.title).includes(subscription.title)) {
+ alert("Subscription name not unique")
+ return false
+ }
+
+ return isValidSubscription(subscription);
+}
+
+let isValidSubscription = (subscription: subscription): boolean => {
+ if (isNaN(subscription.price)) {
+ alert("Amount is not a number")
+ return false
+ }
+ if (!([...currencies.keys()].includes(subscription.currency))) {
+ alert("Currency is not a valid currency")
+ return false
+ }
+ if (isNaN(subscription.months)) {
+ alert("Months are not a number")
+ return false
+ }
+
+ return true
+}
+
+let createNewSubscription = (title, price, currency, months, notes): subscription => {
+ const newSub: subscription = {
+ changed: false,
+ currency: currency,
+ indkk: 0,
+ monthly: 0,
+ months: months,
+ notes: notes,
+ percentOfWhole: null,
+ price: price,
+ title: title
+ }
+
+ newSub.monthly = newSub.price / newSub.months;
+ newSub.indkk = newSub.monthly * currencies.get(newSub.currency);
+
+ return newSub;
+}
+
+// Create a new subscription
+let newSubscription = () => {
+ // Open the popup
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert subscription information',
+ html:
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '',
+ focusConfirm: false,
+ preConfirm: () => {
+ // Create a new temporary subscription
+
+
+ const title = (document.getElementById('swal-input1')).value;
+ const price = parseFloat((document.getElementById('swal-input2')).value)
+ const currency = (document.getElementById('swal-input3')).value
+ const months = parseInt((document.getElementById('swal-input4')).value)
+ const notes = (document.getElementById('swal-input5')).value
+
+ const newSub = createNewSubscription(title, price, currency, months, notes)
+
+ // Do some form validation
+ let valid = isValidNewSubscription(newSub)
+
+ // Update the table if form was validated successfully
+ if (valid){
+ subscriptions.push(newSub)
+ updateTable()
+ }
+ }
+ })
+}
+
+// Edit a subscription
+let editSubscription = (btn) => {
+ // Find out which subscription we're editing
+ let currentSubscription = getSubscriptionFromButton(btn)
+
+ // Open the popup, and fill in the current subscription information in the form
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert subscription information',
+ html:
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ '',
+ focusConfirm: false,
+ cancelButtonColor: '#d33',
+ showCancelButton: true,
+ preConfirm: () => {
+ // Create a new temporary subscription
+ const title = (document.getElementById('swal-input1')).value;
+ const price = parseFloat((document.getElementById('swal-input2')).value)
+ const currency = (document.getElementById('swal-input3')).value
+ const months = parseInt((document.getElementById('swal-input4')).value)
+ const notes = (document.getElementById('swal-input5')).value
+
+ const editedSub = createNewSubscription(title, price, currency, months, notes)
+
+ let valid: boolean = isValidSubscription(editedSub)
+ console.log(valid)
+
+ if (valid){
+ console.log(editedSub)
+ // Update the table if form was validated successfully
+ deleteSubscription(currentSubscription)
+ subscriptions.push(editedSub)
+ updateTable()
+ }
+ }
+ })
+}
+
+// Add a new currency. Called by the add currency button
+let addCurrency = () => {
+ // Fire popup
+ // @ts-ignore
+ Swal.fire({
+ title: 'Insert currency information',
+ html:
+ '' +
+ '',
+ focusConfirm: false,
+ preConfirm: () => {
+ // Register the new possible currency
+ let newCurrency = {
+ symbol: (document.getElementById('swal-input1')).value,
+ rate: parseFloat((document.getElementById('swal-input2')).value),
+ }
+
+ // Check that the rate is a number
+ if (isNaN(newCurrency.rate)) {
+ alert("DKK exchange rate is now a number")
+ return
+ }
+
+ // Update with the new currency, and refresh the table
+ currencies.set(newCurrency.symbol, newCurrency.rate)
+ updateTable()
+ }
+ })
+}
+
+// Delete all subscriptions.
+let Reset = () => {
+ // @ts-ignore
+ Swal.fire({
+ title: 'Are you sure?',
+ text: "You won't be able to revert this!",
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Yes, delete it!'
+ }).then((result) => {
+ if (result.value) {
+ subscriptions.length = 0;
+ updateTable()
+ // @ts-ignore
+ Swal.fire(
+ 'Reset!',
+ 'Your subscriptions have been reset.',
+ 'success'
+ )
+ }
+ })
+}
+
+
+// Method for updating the table with new information.
+let updateTable = () => {
+ // Create a new replacement tbody
+ let newTbody = document.createElement('tbody');
+ newTbody.classList.add("tbody")
+
+ // Sort by users preference
+
+ // Add rows from data in memory.
+ populateTable(newTbody)
+
+ // Replace the old tbody with the new cleared one
+ let oldTbody = document.querySelector('.tbody')
+ oldTbody.parentNode.replaceChild(newTbody, oldTbody)
+
+ // Save the new information locally.
+ setStored(localDataName, JSON.stringify(getSaveInfo()))
+}
+
+// Populate a tbody with subscriptions from the subscriptions array.
+let populateTable = (tbody) => {
+ // Store the total cost of all subscriptions
+ let total = subscriptions.map((s) => s.monthly).reduce((acc, sub) => acc + sub)
+
+ // Update tablefoot with total monthly cost and amount of subscriptions.
+ document.querySelector('#total').innerHTML = total.toFixed(2);
+ document.querySelector('#amountOfSubscriptions').innerHTML = subscriptions.length.toString();
+
+ // Begin to create table rows
+ for (let i = 0; i < subscriptions.length; i++) {
+ // Select a subscription on index i, and create a new empty row
+ let elem = subscriptions[i]
+ let tr = document.createElement('tr');
+
+ // Calculate the subscriptions percentage-wise cost of the monthly total.
+ elem.percentOfWhole = elem.indkk / total * 100;
+
+ // If this element has been changed (ie. created, moved), mark it for the user.
+ if (elem.changed) {
+ tr.classList.add("is-selected");
+ elem.changed = false
+ }
+
+ // Create the row.
+ tr.innerHTML = '
' + elem.title + '
' +
+ '
' + elem.monthly.toFixed(2) + '
' +
+ '
' + elem.currency + '
' +
+ '
' + elem.indkk.toFixed(2) + '
' +
+ '
' + elem.percentOfWhole.toFixed(2) + '%
' +
+ '
' + '' + '
' +
+ '
' + '' + '
' +
+ '
' + `` + '
';
+
+ // Append the new row
+ tbody.appendChild(tr);
+ }
+}
+
+// Create a pie-chart with the cost of each subscription
+let PieChart = () => {
+ // Get data ready for CanvasJS.
+ let data = []
+ for (let subscription of subscriptions) {
+ console.log(subscription)
+ data.push({y: subscription.percentOfWhole, label: subscription.title})
+ }
+
+ // Create the chart with CanvasJS.
+ // @ts-ignore
+ let chart = new CanvasJS.Chart("chartContainer", {
+ animationEnabled: true,
+ title: {
+ text: "Monthly Expenses"
+ },
+ data: [{
+ type: "pie",
+ startAngle: 240,
+ yValueFormatString: "##0.00\"%\"",
+ indexLabel: "{label} {y}",
+ dataPoints: data
+ }]
+ });
+
+ // Lastly, render it.
+ chart.render()
+}
+
+// Ask the user what to sort by, and then sorts the array.
+let Sort = async () => {
+
+ // The input options for SWAL2
+ const inputOptions = new Promise((resolve) => {
+ resolve({
+ 'Lex': 'Lexi.',
+ 'Price': 'Price'
+ })
+ })
+
+ // Fire the popup
+ // @ts-ignore
+ const {value: sortBy} = await Swal.fire({
+ title: 'Sort by what',
+ input: 'radio',
+ inputOptions: inputOptions,
+ inputValidator: (value) => {
+ if (!value) {
+ return 'You need to choose something!'
+ }
+ }
+ })
+
+ // Sort the array, and save the sorting preference
+ if (sortBy) {
+ if (sortBy == "Lex") {
+ setStored(localSortPreferenceName, "Lex")
+ sortSubscriptionsByName()
+ updateTable()
+ } else if (sortBy == "Price") {
+ setStored(localSortPreferenceName, "Price")
+ sortSubscriptionsByPrice()
+ updateTable()
+ }
+ }
+}
+
+// different sorters for subscriptions array
+let sortSubscriptionsByPrice = () => {
+ subscriptions.sort((a, b) => {
+ if (a.indkk > b.indkk) {
+ return -1;
+ }
+ if (b.indkk > a.indkk) {
+ return 1;
+ }
+ return 0;
+ })
+}
+let sortSubscriptionsByName = () => {
+ subscriptions.sort((a, b) => {
+ if (a.title > b.title) {
+ return 1;
+ }
+ if (b.title > a.title) {
+ return -1;
+ }
+ return 0;
+ })
+}
+
+
+// Gets more information on a subscription, and displays it in a popup
+let getInfoOfSubscription = (btn) => {
+ let currentSub = getSubscriptionFromButton(btn)
+ // @ts-ignore
+ Swal.fire(
+ currentSub.title,
+ `${currentSub.price}${currentSub.currency} over ${currentSub.months} months, which equates to
+ ${currentSub.monthly}${currentSub.currency} monthly, or about ${currentSub.indkk} DKK ` +
+ // Display notes only if there are some.
+ (currentSub.notes === undefined ? "" : (
+ `
+ Notes:
+
+
+
+ `)),
+ 'question'
+ )
+}
+
+// Since title is unique, this finds the title of the row from which a button was pressed, and returns that subscription from the subscription array
+let getSubscriptionFromButton = (button) => {
+ let tr = button.parentNode.parentNode
+ let title = tr.firstChild.textContent
+ return subscriptions.filter(s => s.title == title)[0]
+}
+
+let askToDeleteRow = (btn) => {
+ // @ts-ignore
+ Swal.fire({
+ title: 'Are you sure?',
+ text: "You won't be able to revert this!",
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Yes, delete it!'
+ }).then((result) => {
+ if (result.value) {
+ deleteSubscription(getSubscriptionFromButton(btn))
+ // @ts-ignore
+ Swal.fire(
+ 'Deleted!',
+ 'Your file has been deleted.',
+ 'success'
+ )
+ }
+ })
+}
+
+let deleteSubscription = (subscription) => {
+ const index = subscriptions.map(s => s.title).indexOf(subscription.title);
+ subscriptions.splice(index, 1);
+ updateTable()
+}
+
+// Make subscriptions and currency ready to be stored as JSON
+let getSaveInfo = (): save => {
+ return {version: 1, subscriptions: subscriptions, keys: [...currencies.keys()], values: [...currencies.values()]}
+}
+
+// Parse a save object, and use it as the current info.
+let loadSaveInfo = (loadedSave: save, oldLoadedSave: oldSave) => {
+
+ let actualSaveToUse = loadedSave;
+
+ if (oldLoadedSave !== null){
+ // Old data
+
+ console.log("loading legacy data...")
+
+ const oldData: oldSave = oldLoadedSave;
+
+ let newData: save = {keys: [], subscriptions: [], values: [], version: 0};
+ newData.keys = oldData.keys;
+ newData.version = 1;
+ newData.values = oldData.values;
+
+ for (let sub of oldLoadedSave.subscriptions){
+ const newSub: subscription = createNewSubscription(sub.title, sub.amount, sub.currency, sub.months, sub.notes);
+ newData.subscriptions.push(newSub);
+ }
+
+ actualSaveToUse = newData;
+ }
+
+
+ subscriptions = actualSaveToUse.subscriptions
+
+ currencies.clear()
+ for (let i = 0; i < actualSaveToUse.keys.length; i++) {
+ currencies.set(actualSaveToUse.keys[i], actualSaveToUse.values[i])
+ }
+}
+
+// Save a local JSON version of the current state.
+let saveToFile = () => {
+ var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(getSaveInfo()));
+ var downloadAnchorNode = document.createElement('a');
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", "save" + ".json");
+ document.body.appendChild(downloadAnchorNode); // required for firefox
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+}
+
+// Load a local JSON version of some state.
+let load = () => {
+ var input, file, fr;
+
+ if (typeof window.FileReader !== 'function') {
+ alert("The file API isn't supported on this browser yet.");
+ return;
+ }
+
+ input = document.getElementById('fileinput');
+ if (!input) {
+ alert("Um, couldn't find the fileinput element.");
+ } else if (!input.files) {
+ alert("This browser doesn't seem to support the `files` property of file inputs.");
+ } else if (!input.files[0]) {
+ alert("Please select a file before clicking 'Load'");
+ } else {
+ file = input.files[0];
+ fr = new FileReader();
+ fr.onload = receivedText;
+ fr.readAsText(file);
+ }
+
+ function receivedText(e) {
+ let lines = e.target.result;
+ let loadedSave = JSON.parse(lines);
+
+ if (loadedSave.version == null){
+ loadSaveInfo(null, loadedSave)
+ }
+ else{
+ loadSaveInfo(loadedSave, null)
+
+ }
+
+ updateTable()
+ }
+}
+
+// Retrieve the key value pair vname, vvalue
+function getStored(vname) {
+ if (typeof (Storage) !== "undefined") {
+ return localStorage.getItem(vname)
+ } else {
+ return getCookie(vname)
+ }
+}
+
+// Store the key value pair vname, vvalue
+function setStored(vname, vvalue) {
+ if (typeof (Storage) !== "undefined") {
+ return localStorage.setItem(vname, vvalue)
+ } else {
+ return setCookie(vname, vvalue, 1000)
+ }
+}
+
+
+// Fallback if browser doesn't support HTML5 localstorage
+function setCookie(cname, cvalue, exdays) {
+ var d = new Date();
+ d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
+ var expires = "expires=" + d.toUTCString();
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
+}
+
+function getCookie(cname) {
+ var name = cname + "=";
+ var decodedCookie = decodeURIComponent(document.cookie);
+ var ca = decodedCookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+ return "";
+}
+
+
+// Try to load data from local storage. If this fails, there's probably not any data to retrieve.
+try {
+ let data = JSON.parse(getStored(localDataName));
+
+ if (data.version == null){
+ loadSaveInfo(null, data);
+ }
+ else{
+ loadSaveInfo(data, null)
+ }
+
+ // Find out if we want to sort the array of the users data.
+ let userSortPref = getStored(localSortPreferenceName)
+ if (userSortPref === undefined){
+ sortSubscriptionsByName()
+ }
+ else{
+ if (userSortPref == "Price"){
+ sortSubscriptionsByPrice()
+ }
+ else{
+ sortSubscriptionsByName()
+ }
+ }
+ updateTable()
+} catch (e) {
+ console.log("Didn't find any locally stored data")
+ console.log(e)
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3fd5769
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es2018",
+ "sourceMap": true
+ },
+ "exclude": [
+ "node_modules"
+ ]
+}