// LIDAR var lidarOsd; var context = new AudioContext(); var clockTone = createClockTone(context); var audioPlayer = null; var timerHandle; var timerDelta; var sniperscope = false; var clockVolume = 0.02; var selfTestVolume = 0.02; var recordLimit = -1 var version = -1 var clockToneMute; var databaseRecords = []; var resourceName; var velocityUnit = 'mph' var rangeUnit = 'ft' var speedFilters = [] const imperialSpeedFilters = [0, 20, 30, 40, 50, 60, 70, 80, 90, 100]; const metricSpeedFilters = [0, 20, 40, 60, 80, 100, 120, 140, 160, 180]; var moveMode = false; var initWidth = 1920; var initHeight = 1080; var lastTop = 0; var lastLeft = 0; // TABLET var infowindow; var mapOptions; var roadmap; var map; var dataTable; var speedLimits = {}; var playerName; var imgurApiKey; var discordApiKey; var speedFilter = 0; var mapMarkerPageOption = true var mapMarkerPlayerOption = false var legendWrapper; var currentRecord; var themeMode = 0; // 0-light, 1-dark, 2-auto var tabletTime; var gameTime; var timeDisplayHandle; const darkTime = new Date("1970-01-01T21:30:00"); const lightTime = new Date("1970-01-01T06:15:00"); // Dynamically load map element ensuring no GM API race condition window.initMap = initMap; // Fetch speedlimits json for color coding and filtering. fetch('../../speedlimits.json') .then(response => response.json()) .then(data => { speedLimits = data; }) .catch(error => console.error('Unabled to fetch speedlimits.json:', error)); // Exit tablet hotkey $(document).keyup(function(event) { // Esc if (event.keyCode == 27) { sendDataToLua('CloseTablet', undefined); $('#loading-dialog-container').hide(); $('#view-record-container').hide(); $('#print-result-dialog-container').hide(); } } ); $(document).ready(function () { // Dynamically load script once doc is ready. var googleMapsApiScript = document.createElement('script'); googleMapsApiScript.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyDF6OI8FdmtZmgrTsh1yTa__UlwA52BGEQ&callback=initMap'; googleMapsApiScript.async = true; document.head.appendChild(googleMapsApiScript); lidarOsd = document.getElementById("laser-gun"); initWidth = document.body.clientWidth; initHeight = document.body.clientHeight; $('#hud').hide(); $('#laser-gun').hide(); $('#history-container').hide(); $('#tablet').hide(); $('#loading-dialog-container').hide(); $('#view-record-container').hide(); $('#print-result-dialog-container').hide(); $('#tablet-close').click(function() { mapMarkerPageOption = true; $('#btn-own').prop('checked', false); $('#btn-all-players').prop('checked', true); $('#btn-this-page').prop('checked', true); $('#btn-all-pages').prop('checked', false); mapMarkerPlayerOption = false; dataTable.destroy(); $('#clock-table-container').html( '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Record
ID
TimestampSpeed
(' + velocityUnit + ')
Distance
(' + rangeUnit + ')
PlayerStreetMapPrint
' ) sendDataToLua('CloseTablet', undefined); }); $('#toggle-theme').click(function() { if (themeMode == 0) { themeMode = 1; } else if(themeMode == 1) { themeMode = 2; } else { themeMode = 0; } RefreshTheme(); sendDataToLua('SendTheme', themeMode); }); $('#print-view-print').click( function() { if (imgurApiKey != '' || discordApiKey != ''){ $('#tablet').fadeOut(); $('.print-view-header').css('opacity', '0'); $('#view-record').addClass('no-border'); captureScreenshot(); setTimeout(function(){ $('#tablet').fadeIn(); $('.print-view-header').css('opacity', '1'); $('#view-record').removeClass('no-border'); }, 1000) } else { $('#copy-button').hide(); $('#dialog-msg').text("
Upload Failed
"); $('#url-display-imgur').text("No Imgur or Discord integration set. Contact a server developer."); $('#print-result-dialog-container').fadeIn(); } }); $('#print-view-close').click( function() { map.setOptions({ zoomControl: true, }); $('#view-record-container').fadeOut(); $('.legend-wrapper').show(); updateMarkers(); }); $('#copy-button').click(function() { var urlDisplay = $('#url-display-discord').text(); if (urlDisplay === '') { urlDisplay = $('#url-display-imgur').text(); } urlDisplay = urlDisplay.split(' ')[1]; var textarea = document.createElement('textarea'); textarea.value = urlDisplay; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); $('#copy-button').text("Link Copied"); }); $('#print-dialog-close').click( function() { $('#print-result-dialog-container').fadeOut(); }); window.addEventListener('message', function (event) { if (event.data.action == 'SetLidarDisplayState') { if (event.data.state) { $('#laser-gun').fadeIn(); } else { $('#laser-gun').fadeOut(); } } else if (event.data.action == 'SendClockData') { $('#speed').text(event.data.speed); $('#range').text(event.data.range + rangeUnit); $('#range-hud').text(event.data.range + rangeUnit); $('#timer').text(''); $('#lock').hide(); $('#arrowup').hide(); $('#arrowdown').hide(); if (event.data.towards == true) { $('#speed-hud').text('- ' + event.data.speed); $('#arrowup').hide(); $('#arrowdown').show(); timer(); clearInterval(clockToneMute); playClockTone(); } else if (event.data.towards == false) { $('#speed-hud').text('+ ' + event.data.speed); $('#arrowdown').hide(); $('#arrowup').show(); timer(); clearInterval(clockToneMute); playClockTone(); } else { $('#speed-hud').text('/ ' + event.data.speed); clearInterval(clockToneMute); clockTone.vol.gain.exponentialRampToValueAtTime(0.00001,context.currentTime + 0.1); clearInterval(timerHandle); } } else if (event.data.action == 'SetDisplayMode') { if (event.data.mode == 'ADS') { $('#hud').show(); $('#laser-gun').hide(); } else { $('#hud').hide(); $('#laser-gun').show(); } } else if (event.data.action == 'SetSelfTestState') { if (event.data.state) { clearInterval(timerHandle); $('#timer').text(''); $('#lock').hide(); $('#lidar-home').show(); $('#self-test-container').hide(); if (event.data.sound) { playSound('LidarCalibration'); } } else { $('#lidar-home').hide(); $('#self-test-container').show(); $('#self-test-timer').show(); timer(); } } else if (event.data.action == 'SendSelfTestProgress') { $('#self-test-progress').text(event.data.progress); if (event.data.stopTimer){ $('#self-test-timer').hide(); } } else if (event.data.action == 'scopestyle') { if (sniperscope) { $('#hud').css('background-image', 'url(textures/hud_sight.png)' ); } else { $('#hud').css('background-image', 'url(textures/hud_sniper.png)' ); } sniperscope = !sniperscope; } else if (event.data.action == 'SetConfigVars') { selfTestVolume = event.data.selfTestSFX; clockVolume = event.data.clockSFX; imgurApiKey = event.data.imgurApiKey; discordApiKey = event.data.discordApiKey; recordLimit = event.data.recordLimit; resourceName = event.data.name; version = event.data.version; $('#tablet-version').text('v'+version); themeMode = event.data.theme; RefreshTheme(); if (event.data.osdStyle != false){ event.data.osdStyle = JSON.parse(event.data.osdStyle); $('#laser-gun').css("left", event.data.osdStyle.left); $('#laser-gun').css("top", event.data.osdStyle.top); $('#laser-gun').css("transform", 'scale('+event.data.osdStyle.scale+')'); } if (event.data.metric) { speedFilters = metricSpeedFilters; velocityUnit = 'km/h'; rangeUnit = 'm'; $('#unit').text(velocityUnit) $('.speed').html('Speed
(' + velocityUnit + ')') $('.distance').html('Distance
(' + rangeUnit + ')') } else { speedFilters = imperialSpeedFilters; velocityUnit = 'mph'; rangeUnit = 'ft'; } } else if (event.data.action == 'SetHistoryState') { if (event.data.state) { $('#lidar-home').hide(); $('#history-container').show(); } else { $('#lidar-home').show(); $('#history-container').hide(); } } else if (event.data.action == 'SendHistoryData') { $('#counter').text(event.data.counter); $('#timestamp').text('Date Time: ' + event.data.time); $('#clock').text('Speed Range: ' + event.data.clock); } else if (event.data.action == 'PlayButtonPressBeep') { playSound(event.data.file); } else if (event.data.action == 'SendBatteryAmount') { $('#battery').attr('src', 'textures/battery' + event.data.bars + '.png' ); } else if (event.data.action == 'GetCurrentDisplayData') { var returnData = { } returnData.onHistory = $('#history-container').is(':visible') ? true : false; if (returnData.onHistory) { returnData.counter = $('#counter').text(); returnData.time = $('#timestamp').text().replace('Date Time: ', ''); returnData.clock = $('#clock').text().replace('Speed Range: ', ''); } else { returnData.speed = $('#speed').text(); returnData.range = $('#range').text().replace(rangeUnit, ''); if ($('#arrowup').is(':visible')){ returnData.arrow = 1; } else if ($('#arrowdown').is(':visible')) { returnData.arrow = -1; } else { returnData.arrow = 0; } returnData.elapsedTime = timerDelta; returnData.battery = $('#battery').attr('src'); } sendDataToLua('ReturnCurrentDisplayData', returnData); } else if (event.data.action == 'SendPeersDisplayData') { $('#speed').text(event.data.speed); $('#range').text(event.data.range + rangeUnit); if ( event.data.arrow == 1){ $('#arrowup').show(); $('#arrowdown').hide(); } else if ( event.data.arrow == -1 ) { $('#arrowup').hide(); $('#arrowdown').show(); } else { $('#arrowup').hide(); $('#arrowdown').hide(); } $('#battery').attr('src', event.data.battery ); if (event.data.range != '----' + rangeUnit) { timer(event.data.elapsedTime); } } else if (event.data.action == 'SendDatabaseRecords') { playerName = event.data.name; // clock display updateClock(); timeDisplayHandle = setInterval(updateClock, 60000); databaseRecords = JSON.parse(event.data.table); updateTabletWindow(playerName, databaseRecords); // time based night mode handling gameTime = date = new Date("1970-01-01"); const [hours, minutes, seconds] = event.data.time.split(":"); date.setFullYear(1970, 0, 1); gameTime.setHours(hours); gameTime.setMinutes(minutes); if (themeMode == 2) { if (gameTime > darkTime || gameTime < lightTime) { $("#theme").attr("href", "dark.css"); } else { $("#theme").attr("href", ""); } } } else if (event.data.action == 'SetTabletState') { if (!event.data.state) { $('#tablet').fadeOut(); clearInterval(timeDisplayHandle); } } else if (event.data.action == 'SendResizeAndMove') { if (event.data.reset) { lidarOsd.style.top = "unset"; lidarOsd.style.bottom = "2%"; lidarOsd.style.left = "60%"; lidarOsd.style.transform = "scale(0.65)"; ReturnOsdStyle() } else { lidarOsd.addEventListener("wheel", handleScrollResize); lidarOsd.addEventListener("mousedown", dragMouseDown); moveMode = true; $('#laser-gun').css('pointer-events', 'all'); } } }); }); // ======= MAIN SCRIPT ======= // This function is used to send data back through to the LUA side function sendDataToLua( name, data ) { $.post( "https://"+ resourceName +"/" + name, JSON.stringify( data ), function( datab ) { if ( datab != "ok" ) { console.log( datab ); } } ); } // Credit to xotikorukx playSound Fn. function playSound(file) { if (audioPlayer != null) { audioPlayer.pause(); } audioPlayer = new Audio('./sounds/' + file + '.ogg'); audioPlayer.volume = selfTestVolume; var didPlayPromise = audioPlayer.play(); if (didPlayPromise === undefined) { audioPlayer = null; //The audio player crashed. Reset it so it doesn't crash the next sound. } else { didPlayPromise .then(_ => {}) .catch(error => { //This does not execute until the audio is playing. audioPlayer = null; //The audio player crashed. Reset it so it doesn't crash the next sound. }); } } function createClockTone(audioContext) { let osc = audioContext.createOscillator(); let vol = audioContext.createGain(); osc.type = 'sine'; osc.frequency.value = 0.5; vol.gain.value = 0.02; osc.connect(vol); vol.connect(audioContext.destination); osc.start(0); return { osc: osc, vol: vol }; } String.prototype.toHHMMSS = function () { var sec_num = parseInt(this, 10); var minutes = Math.floor(sec_num / 60000); var seconds = Math.floor((sec_num - minutes * 60000) / 1000); if (minutes < 10) { minutes = '0' + minutes; } if (seconds < 10) { seconds = '0' + seconds; } return minutes + ':' + seconds; }; function timer( elapsedTime = 0 ) { var start = Date.now() - elapsedTime clearInterval(timerHandle); timerHandle = setInterval(function () { timerDelta = Date.now() - start; // milliseconds elapsed since start $('#lock').show(); $('#timer').show(); $('#timer').text(timerDelta.toString().toHHMMSS()); $('#self-test-timer').text(timerDelta.toString().toHHMMSS()); }, 500); // update about every second } function playClockTone() { clockTone.osc.frequency.exponentialRampToValueAtTime( 2300, context.currentTime + 0.1 ); clockTone.vol.gain.exponentialRampToValueAtTime( clockVolume, context.currentTime + 0.01 ); clockToneMute = setInterval(function () { clockTone.vol.gain.exponentialRampToValueAtTime( 0.00001, context.currentTime + 0.1 ); }, 300); // update about every second } // Move Mode // Drag to move functions below. // Exit HUD Move Mode $(document).keyup(function(event) { if (moveMode) { // Esc Backspace Space if (event.keyCode == 27 || event.keyCode == 9 || event.keyCode == 32) { ReturnOsdStyle(); } } } ); $(document).contextmenu(function() { if (moveMode) { ReturnOsdStyle(); } } ); function ReturnOsdStyle() { var computedStyles = window.getComputedStyle(lidarOsd); var left = computedStyles.getPropertyValue("left"); var top = computedStyles.getPropertyValue("top"); var transform = computedStyles.transform; var newScale = 0.65; if (transform && transform !== "none") { var matrixMatch = transform.match(/^matrix\((.+)\)$/); if (matrixMatch && matrixMatch.length > 1) { var matrixValues = matrixMatch[1].split(", "); var scale = parseFloat(matrixValues[0]); if (!isNaN(scale)) { newScale = scale } } } sendDataToLua( "ReturnOsdScaleAndPos", data = { left: left, top: top, scale: newScale } ); moveMode = false; $('#laser-gun').css('pointer-events', 'none'); } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // get the mouse cursor position at startup: pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // call a function whenever the cursor moves: document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // set the element's new position: lidarOsd.style.top = (lidarOsd.offsetTop - pos2) + "px"; lidarOsd.style.left = (lidarOsd.offsetLeft - pos1) + "px"; } function closeDragElement() { // stop moving when mouse button is released: document.onmouseup = null; document.onmousemove = null; } function handleScrollResize(event) { var currentScale = parseFloat($(lidarOsd).css("transform").replace("matrix(", "").split(",")[0]); if (isNaN(currentScale)) { console.log("Scale not previously set on " + lidarOsd.id + ", using 1.0"); currentScale = 0.65; } var deltaY = Math.sign(event.deltaY); var newScale = currentScale + (deltaY < 0 ? 0.05 : -0.05); if (newScale < 0.3) { newScale = 0.3; } else if (newScale > 1.0){ newScale = 1.0; } $(lidarOsd).css("transform", "scale(" + newScale + ")"); } // Handle Resolution Changes -> Restore Position $(window).resize(function() { if (document.body.clientWidth != initWidth || document.body.clientHeight != initHeight) { lastTop = lidarOsd.style.top; lastLeft = lidarOsd.style.left; lidarOsd.style.top = "unset"; lidarOsd.style.bottom = "2%"; lidarOsd.style.left = "60%"; sendDataToLua( "ResolutionChange", data = { restore: false } ); } else { lidarOsd.style.top = lastTop; lidarOsd.style.left = lastLeft; sendDataToLua( "ResolutionChange", data = { restore: true } ); } }); // ===== END MAIN SCRIPT ====== // ========= TABLET ========= function initMap(){ infowindow = new google.maps.InfoWindow() mapOptions = { center: new google.maps.LatLng(0, 0), zoom: 2, minZoom: 2, streetViewControl: false, mapTypeControl: false, gestureHandling: 'greedy', }; // Define our custom map type roadmap = new google.maps.ImageMapType({ getTileUrl: function (coords, zoom) { if ( coords && coords.x < Math.pow(2, zoom) && coords.x > - 1 && coords.y < Math.pow(2, zoom) && coords.y > -1 ) { return ( 'textures/map/roadmap/' + zoom + '_' + coords.x + '_' + coords.y + '.jpg' ); } else { return 'textures/map/roadmap/empty.jpg'; } }, tileSize: new google.maps.Size(256, 256), maxZoom: 5, minZoom: 2, zoom: 2, name: 'Roadmap', }); // --------------------- // init map map = new google.maps.Map( document.getElementById('map'), mapOptions ); map.mapTypes.set('gta_roadmap', roadmap); // sets default 'startup' map map.setMapTypeId('gta_roadmap'); // Define an array of markers with custom icons and labels var markers = [ { icon: '', label: '
Own
' }, { icon: 'textures/map/green-dot-light.png', label: '< Speedlimit' }, { icon: 'textures/map/yellow-dot-light.png', label: '> Speedlimit' }, { icon: 'textures/map/red-dot-light.png', label: '> Speedlimit by 10 ' + velocityUnit +'+' }, { icon: '', label: '
Peers
' }, { icon: 'textures/map/green-dot.png', label: '< Speedlimit' }, { icon: 'textures/map/yellow-dot.png', label: '> Speedlimit' }, { icon: 'textures/map/red-dot.png', label: '> Speedlimit by 10 ' + velocityUnit + '+' } ]; // Create a new legend control var legend = document.createElement('div'); legend.classList.add('legend-container'); // Loop through the markers array and add each marker to the legend control markers.forEach(function(marker) { var icon = marker.icon; var label = marker.label; var legendItem = document.createElement('div'); legendItem.classList.add('legend-item'); var iconImg = document.createElement('img'); iconImg.setAttribute('src', icon); legendItem.appendChild(iconImg); var labelSpan = document.createElement('span'); labelSpan.innerHTML = label; legendItem.appendChild(labelSpan); legend.appendChild(legendItem); }); legendWrapper = document.createElement('div'); legendWrapper.classList.add('legend-wrapper'); legendWrapper.appendChild(legend); } function gtamp2googlepx(x, y) { // IMPORTANT // for this to work #map must be width:1126.69px; height:600px; // you can change this AFTER all markers are placed... //-------------------------------------- //conversion increment from x,y to px,py var mx = 0.0503; var my = -0.0503; //-0.05003 //math mVAR * cVAR var x = mx * x; var y = my * y; //offset for correction var x = x - 486.97; var y = y + 408.9; //return latlong coordinates return [x, y]; } // Marker Function function addMarker(id, x, y, content_html, icon) { //to ingame 2 google coords here, use function. var coords = gtamp2googlepx(x, y); var location = overlay .getProjection() .fromContainerPixelToLatLng( new google.maps.Point(coords[0], coords[1]) ); var marker = new google.maps.Marker({ position: location, map: null, icon: 'textures/map/' + icon + '.png', optimized: false, //to prevent it from repeating on the x axis. }); databaseRecords[id].googleLoc = location; databaseRecords[id].marker = marker; //when you click anywhere on the map, close all open windows... google.maps.event.addListener(marker, 'click', function () { infowindow.setContent(content_html); infowindow.open(map, marker); map.setCenter(new google.maps.LatLng(location)); map.setZoom(6); google.maps.event.addListener(map, 'click', function () { infowindow.close(); }); }); } function openInfo(element) { var elementRecord = databaseRecords[element.id]; map.setCenter(new google.maps.LatLng(elementRecord.googleLoc)); map.setZoom(5); infowindow.setContent(elementRecord.infoContent); infowindow.open(map, elementRecord.marker); } var loadedAlready = false // Main window update function function updateTabletWindow(playerName, databaseRecords){ $('#tablet').fadeIn(); $('#loading-dialog-container').fadeIn(); overlay = new google.maps.OverlayView(); overlay.draw = function () {}; overlay.setMap(map); if (!loadedAlready){ google.maps.event.addListenerOnce(map, 'tilesloaded', function () { loadedAlready = true; setTimeout(function() { processRecords(playerName, databaseRecords); }, 100) }); } else { $('#map').attr("style", ""); map = new google.maps.Map(document.getElementById('map'), mapOptions); overlay = new google.maps.OverlayView(); overlay.draw = function () {}; overlay.setMap(map); map.mapTypes.set('gta_roadmap', roadmap); map.setMapTypeId('gta_roadmap'); google.maps.event.addListenerOnce(map, 'tilesloaded', function () { setTimeout(function() { processRecords(playerName, databaseRecords); }, 100) }); } } function processRecords(playerName, databaseRecords){ // Iterate through all records dynamically creating table, markers var tBodyRows = [] for (var i = 0; i < databaseRecords.length; i++) { var record = databaseRecords[i]; // Speedlimit conditional formatting var primaryStreet = record.street.includes('/') ? [record.street.split('/')[0].trim()] : [record.street.trim()]; var markerColor = 'green-dot'; var speedString; var speedLimit = speedLimits[primaryStreet]; if (speedLimit === undefined ) { speedString = '' + record.speed + ''; console.log('^3Unable to locate speed limit of', primaryStreet); } else { databaseRecords[i].speedlimit = speedLimit; if (record.speed < speedLimit) { speedString = '' + record.speed + ''; markerColor = 'green-dot'; } else if (record.speed > speedLimit + 10) { speedString = '' + record.speed + ''; markerColor = 'red-dot'; } else if (record.speed > speedLimit) { speedString = '' + record.speed + ''; markerColor = 'yellow-dot'; } } // Generate marker info window content record.infoContent = '
RID: ' + record.rid + '
' + record.speed + velocityUnit + '
' + record.player + '
'; // Is own record conditional marker formatting if ( record.player == playerName ) { markerColor = markerColor + '-light' } // Add markers to map addMarker(i, record.targetX, record.targetY, record.infoContent, markerColor); // Add records to table tBodyRows.push( '' + record.rid + '' + '' + record.timestamp + '' + speedString + '' + record.range + '' + '' + record.player + '' + '' + record.street + '' + '' + '' ); } $('#tBody').append(tBodyRows.join('')); // Now that all GMap elements have been correctly caluated, update css to custom position. // Regenerate dataTable after inserting new tBody elements // inefficent should be using dataTable.add() but conditional formatting; lazy; $('#loading-message').html(' Building Table..'); dataTable = $('#clock-table').DataTable({ destroy: true, bPaginate: true, bLengthChange: false, bFilter: true, bInfo: true, bAutoWidth: false, "order": [[ 1, 'desc' ]], "aoColumnDefs": [ { "bSortable": false, "aTargets": [ 6, 7 ] } ], "initComplete": function(settings, json) { // only display markers on this page $('#clock-table').DataTable().on('draw.dt', function() { updateMarkers(); //limited retrieval datatable footer var rows = $('#clock-table').DataTable().rows().count(); if (rows == recordLimit) { var info = $('#clock-table_info'); var text = info.text(); var newText = text + " (limited by config)"; info.text(newText); } }); // dynamic row calulation var containerHeight = $('#clock-table-container').height(); var rowHeight = $('#clock-table tbody tr:first-child').height(); var numRows = Math.floor(containerHeight / rowHeight); $('#clock-table').DataTable().page.len(numRows).draw(); } }); $('#loading-message').html(' Configuring Filters..'); // Table speed filter handling, Drop down creation. var speedFilterDropdown = ''; $('#clock-table_filter').append(speedFilterDropdown); // Table speed filter handling $('.dropdown-menu a').click(function () { speedFilter = Number($(this).attr('value')) $.fn.dataTable.ext.search.push(function ( settings, data, dataIndex ) { return Number(data[2]) > speedFilter ? true : false; }); dataTable.draw(); $.fn.dataTable.ext.search.pop(); // after filtering table, update visible markers to match table updateMarkers(); }); // Map marker filters // Get all the button elements in the button groups const buttons = document.querySelectorAll('.btn-group input[type="radio"]'); // Loop through each button and add a click event listener buttons.forEach(button => { button.addEventListener('click', () => { // Get the value of the clicked button const buttonValue = button.nextElementSibling.textContent.trim(); if (buttonValue == "All"){ mapMarkerPageOption = false updateMarkers() } else if (buttonValue == "This Page") { mapMarkerPageOption = true updateMarkers() } else if (buttonValue == "Own") { mapMarkerPlayerOption = true updateMarkers() } else if (buttonValue == "All Players") { mapMarkerPlayerOption = false updateMarkers() } else if (buttonValue == "Off"){ $('.legend-wrapper').addClass('hidden'); } else if (buttonValue == "On"){ $('.legend-wrapper').removeClass('hidden'); } }); }); // Add the legend after reinitalization $('#loading-message').html(' Repositioning Map..'); document.getElementById('map').style.cssText = 'position: relative; width: 40%; height: calc(100% - 116px); overflow: hidden; float: right; border: inset; z-index: 1; opacity:1;'; map.controls[google.maps.ControlPosition.TOP_LEFT].push(legendWrapper); $('#loading-dialog-container').fadeOut(); } // updates markers based on mapMarkerPageOption mapMarkerPlayerOption function updateMarkers() { var idsArray = []; if (mapMarkerPageOption) { var currentPageNodes = $('#clock-table').DataTable().rows({ page: 'current' }).nodes(); $(currentPageNodes).each(function() { var node = $(this) var rowPlayerName = node.find('td:eq(4)').text(); if (mapMarkerPlayerOption == false || mapMarkerPlayerOption && rowPlayerName == playerName) { var speed = Number(node.find('td:eq(2)').text()); if (speed >= speedFilter){ var id = parseInt(node.find('td:eq(6) button:last-child').prop('id')); idsArray.push(id); } } }); } else { $('#clock-table').DataTable().rows().every(function() { var node = $(this.node()) var rowPlayerName = node.find('td:eq(4)').text(); if (mapMarkerPlayerOption == false || mapMarkerPlayerOption && rowPlayerName.trim() === playerName) { var speed = Number(node.find('td:eq(2)').text()); if (speed >= speedFilter){ var id = parseInt(node.find('td:eq(6) button:last-child').prop('id')); idsArray.push(id); } } }); } hideMarkersExcept(databaseRecords, idsArray); } // update what markers should be shown function filterMarkersBySpeed(dataList, speedFilter) { dataList.forEach(function(data) { if (Number(data.speed) > speedFilter) { if (data.marker) { data.marker.setMap(map); } } else { if (data.marker) { data.marker.setMap(null); } } }); } function hideMarkersExcept(dataList, activeMarkers) { for (let i = 0; i < dataList.length; i++) { if (activeMarkers.includes(i)) { dataList[i].marker.setMap(map); } else { dataList[i].marker.setMap(null); } } } function showAllMarkers(dataList) { for (let i = 0; i < dataList.length; i++) { dataList[i].marker.setMap(map); } } // ==== PRINT VIEW ==== function openPrintView(element) { currentRecord = databaseRecords[element.id]; if ('serial' in currentRecord){ $('#serial').text(currentRecord.serial); } else { var serial = generateSerial() databaseRecords[element.id].serial = serial $('#serial').text(serial); } $('#playerName').text(currentRecord.player); if(currentRecord.selfTestTimestamp != "00/00/0000 00:00") { $('#self-test-time').text(currentRecord.selfTestTimestamp); $('.testResult').addClass('pass'); $('.testResult').text('PASS'); } else { $('#self-test-time').text('N/A'); $('.testResult').removeClass('pass'); $('.testResult').text('N/A'); } $('#recID').text(currentRecord.rid); $('#recDate').text(currentRecord.timestamp); $('#recSpeed').text(currentRecord.speed + ' ' + velocityUnit); $('#recRange').text(currentRecord.range + ' ' + rangeUnit); $('#recStreet').text(currentRecord.street); openInfo(element); // open marker hideMarkersExcept(databaseRecords, [Number(element.id)]); // hide infowindow infowindow.close(); $('.legend-wrapper').hide() // access Date const now = new Date(); const formattedDateTime = now.toISOString().replace('T', ' ').replace(/\.\d{3}Z/, '').slice(0, 16); $('#print-footer-date').text(formattedDateTime); map.setOptions({ zoomControl: false, fullscreenControl: false, }); // copy map window setTimeout(function(){ document.getElementById('print-map').innerHTML = document.getElementById('map').innerHTML; document.getElementById('print-map').style.cssText = "position: relative; width: 400px; height: 275px; overflow: hidden; margin: auto;"; $('#view-record-container').fadeIn(); }, 1000) } // MISC FUNCTIONS PRINTING function generateSerial() { var characters = "ABCDEFGHJKLMNPQRSTUVWXYZ" var randCharIndex1 = Math.floor(Math.random() * characters.length); var randCharIndex2 = Math.floor(Math.random() * characters.length); var char1 = characters.charAt(randCharIndex1); var char2 = characters.charAt(randCharIndex2); var randNum1 = Math.floor(Math.random() * (99 - 10) + 10).toString(); var randNum2 = Math.floor(Math.random() * (999 - 100) + 100).toString(); var serial = '100'+char1+randNum1+char2+randNum2 return serial } function captureScreenshot() { html2canvas(document.querySelector("#view-record"), {scale: '1.5'}).then(canvas => { const imgData = canvas.toDataURL('image/png'); if (imgurApiKey != ''){ var dataUrl = imgData.replace(/^data:image\/(png|jpg);base64,/, ""); uploadImageToImgur(dataUrl); } if (discordApiKey != ''){ uploadImageToDiscord(imgData); } }); } function uploadImageToImgur(dataUrl) { var apiUrl = 'https://api.imgur.com/3/image'; var headers = { 'Authorization': imgurApiKey }; var body = new FormData(); body.append('image', dataUrl); fetch(apiUrl, { method: 'POST', headers: headers, body: body }) .then(function(response) { if (response.ok) { response.json().then(function(data) { console.log('Image uploaded to Imgur. URL:', data.data.link); $('#copy-button').show(); $('#copy-button').text("Copy to Clipboard"); $('#dialog-msg').html("
Uploaded Successfully
"); $('#url-display-imgur').html('Imgur: ' + data.data.link); $('#print-result-dialog-container').fadeIn(); }); } else { throw new Error('Failed to upload image. Status: ' + response.status); } }) .catch(function(error) { console.log('Image failed to upload to Imgur', response.statusText); $('#copy-button').hide(); $('#dialog-msg').text("
Upload Failed
"); $('#url-display-imgur').html('Imgur: ' + error); $('#print-result-dialog-container').fadeIn(); }); } function uploadImageToDiscord(dataUrl) { // Convert the base64 image data to a Blob object var byteString = atob(dataUrl.split(',')[1]); var mimeType = dataUrl.split(',')[0].split(':')[1].split(';')[0]; var arrayBuffer = new ArrayBuffer(byteString.length); var uint8Array = new Uint8Array(arrayBuffer); for (var i = 0; i < byteString.length; i++) { uint8Array[i] = byteString.charCodeAt(i); } var blob = new Blob([arrayBuffer], { type: mimeType }); // Create a FormData object var formData = new FormData(); formData.append('file', blob, 'record-' + currentRecord.rid + '.png'); const now = new Date(); const formattedDateTime = now.toISOString().replace('T', ' ').replace(/\.\d{3}Z/, '').slice(0, 16); var embedData = { color: 11730954, title: 'Speed Event Record', description: '', fields: [ { name: '', value: '-------------------------------------------------------------------------------------', inline: false, }, { name: 'RID:', value: currentRecord.rid, inline: true, }, { name: 'Timestamp:', value: currentRecord.timestamp, inline: true, }, { name: 'User:', value: currentRecord.player, inline: true, }, { name: 'Est. Speed:', value: currentRecord.speed + ' ' + velocityUnit, inline: true, }, { name: 'Est. Distance:', value: currentRecord.range + ' ' + rangeUnit, inline: true, }, { name: 'Est. Geo Location:', value: currentRecord.street, inline: true, }, { name: 'Est. Speed Limit:', value: currentRecord.speedlimit + ' ' + velocityUnit, inline: true, }, { name: '', value: '-------------------------------------------------------------------------------------', inline: false, }, ], image: { url: 'attachment://record-' + currentRecord.rid + '.png', }, footer: { text: 'Accessed: ' + formattedDateTime, }, }; formData.append('payload_json', JSON.stringify({ username: 'ProLaser4', avatar_url: 'https://i.imgur.com/YY12jV8.png', content: '', embeds: [embedData], })); formData.append('file', blob, 'record.png'); fetch(discordApiKey, { method: 'post', body: formData, }) .then(function(response) { if (response.ok) { response.json().then(function(data) { if (data.embeds && data.embeds.length > 0 && data.embeds[0].image && data.embeds[0].image.url){ console.log('attachment found') console.log('Image uploaded to Discord. URL:', data.embeds[0].image.url); $('#copy-button').show(); $('#copy-button').text("Copy to Clipboard"); $('#dialog-msg').text("
Uploaded Successfully
"); $('#url-display-discord').html('Discord: ' + data.embeds[0].image.url); $('#print-result-dialog-container').fadeIn(); } }); } else { throw new Error('Failed to upload image. Status: ' + response.status); } }) .catch(function(error) { console.log('Image failed to upload to Discord', response.statusText); $('#copy-button').hide(); $('#dialog-msg').text("
Upload Failed
"); $('#url-display-discord').html('Discord: ' + error); $('#print-result-dialog-container').fadeIn(); }); } function retrieveRecordFromMarker(text) { if ($('#clock-table').DataTable().search() == ''){ $('#clock-table').DataTable().search(text).draw(); } else { $('#clock-table').DataTable().search('').draw(); } } function RefreshTheme(){ if (themeMode == 0) { $("#theme").attr("href", ""); $("#theme-text").text(' D'); } else if(themeMode == 1) { $("#theme").attr("href", "dark.css"); $("#theme-text").text(' N') } else { if (gameTime && darkTime && lightTime){ if (gameTime > darkTime || gameTime < lightTime) { $("#theme").attr("href", "dark.css"); } else { $("#theme").attr("href", ""); } } $("#theme-text").text(' A') } } function updateClock() { var dateTimeElement = document.getElementById('date-time'); var currentTime = new Date(); // Extract the individual components var month = currentTime.getUTCMonth() + 1; // Months are zero-based var day = currentTime.getUTCDate(); var year = currentTime.getUTCFullYear(); var hours = currentTime.getUTCHours(); var minutes = currentTime.getUTCMinutes(); // Add leading zeros if necessary day = day < 10 ? '0' + day : day; hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; // Create the formatted date-time string var dateTimeString = month + '/' + day + '/' + year + ' ' + hours + ':' + minutes; dateTimeElement.textContent = dateTimeString; }