// Setup 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 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) => { // 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.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 // @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 = 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 not 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 if (subscriptions.length <= 0) { return; } let total = subscriptions.map((s) => s.indkk).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 = () => { 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, 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(); }; // 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); } //# sourceMappingURL=main.js.map