main.js (4761B)
1 // This file is part of the devShort project under the MIT License. Visit https://github.com/flokX/devShort for more information. 2 3 // Register variables 4 const currentDate = new Date(); 5 const startDate = new Date(new Date().setFullYear(currentDate.getFullYear() - 1)); 6 const spinner = document.getElementById('spinner'); 7 const template = document.getElementById('chart-template'); 8 const version = "v3.0.0"; 9 10 // Helper function to post to page api 11 function post(url, data) { 12 'use strict'; 13 return fetch(url, { 14 method: 'POST', 15 headers: { 16 'Content-Type': 'application/json' 17 }, 18 body: JSON.stringify(data) 19 }).then(function (response) { 20 return response.json(); 21 }); 22 } 23 24 // Vue chart component 25 Vue.component('chart', { 26 props: ['name', 'stats'], 27 data: function () { 28 return { 29 identifier: Math.floor(Math.random() * 10000), 30 accessCount: { sevenDays: 0, total: 0 } 31 } 32 }, 33 template: template, 34 methods: { 35 render: function () { 36 let dataset = []; 37 this.accessCount = { sevenDays: 0, total: 0 }; 38 for (let [isoDate, count] of Object.entries(this.stats)) { 39 let date = new Date(isoDate); 40 if (((currentDate - date) / (60 * 60 * 24 * 1000)) <= 7) { 41 this.accessCount.sevenDays += count; 42 } 43 this.accessCount.total += count; 44 dataset.push({ x: date, y: count }); 45 } 46 new frappe.Chart('div#' + this.chartId, { 47 type: 'heatmap', 48 title: 'Access statistics for ' + this.name, 49 data: { 50 dataPoints: this.stats, 51 start: startDate, 52 end: currentDate 53 }, 54 countLabel: 'Accesses', 55 discreteDomains: 0 56 }); 57 }, 58 remove: function (event) { 59 post('admin.php?delete', { 60 name: this.name 61 }).then(function (response) { 62 vm.loadData(); 63 }); 64 } 65 }, 66 computed: { 67 chartId: function () { 68 return 'heatmap-' + this.identifier; 69 }, 70 shortlinkUrl: function () { 71 return this.$parent.dataObject.shortlinks[this.name]; 72 } 73 }, 74 watch: { 75 stats: function () { 76 this.render(); 77 } 78 }, 79 mounted: function () { 80 this.render(); 81 } 82 }); 83 84 // Vue app instance 85 var vm = new Vue({ 86 el: '#app', 87 data: { 88 dataObject: [], 89 loaded: false, 90 search: '' 91 }, 92 methods: { 93 loadData: function (event) { 94 if (event) { 95 // When calling this function via a card-link 96 event.preventDefault(); 97 } 98 this.loaded = false; 99 var vm = this; 100 fetch('admin.php?get_data') 101 .then(function (response) { 102 return response.json() 103 }) 104 .then(function (data) { 105 vm.dataObject = data 106 }); 107 this.loaded = true; 108 }, 109 displayStyle: function (name) { 110 if (!name.toLowerCase().includes(this.search.toLowerCase())) { 111 return 'display: none;' 112 } else { 113 return 'display: block;' 114 } 115 } 116 }, 117 created: function () { 118 this.loadData(); 119 } 120 }); 121 122 // Add a new shortlink 123 document.getElementById('add-form').addEventListener('submit', function (event) { 124 'use strict'; 125 event.preventDefault(); 126 post('admin.php?add', { 127 name: document.getElementById('name').value, 128 url: document.getElementById('url').value 129 }).then(function (data) { 130 if (data.status === 'successful') { 131 document.getElementById('name').value = ''; 132 document.getElementById('url').value = 'https://'; 133 vm.loadData(); 134 document.getElementById('status').innerHTML = ''; 135 } else if (data.status === 'unvalid-url') { 136 document.getElementById('status').insertAdjacentHTML('afterbegin', '<div class="alert alert-danger mt-2" role="alert">Unvalid URL. Please provide a valid URL.</div>'); 137 } else if (data.status === 'url-already-exists') { 138 document.getElementById('status').insertAdjacentHTML('afterbegin', '<div class="alert alert-danger mt-2" role="alert">Shortlink already exists. Please delete before readding.</div>'); 139 } else { 140 document.getElementById('status').insertAdjacentHTML('afterbegin', '<div class="alert alert-danger mt-2" role="alert">Error. Please try again.</div>'); 141 } 142 }); 143 });