Files
spot/script/gaia_upload.js

786 lines
28 KiB
JavaScript

// ==UserScript==
// @name GaiaGps Uploader
// @version 3.0
// @grant none
// @match https://www.gaiagps.com/map/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.0/jquery.min.js
// @namespace https://greasyfork.org/users/583371
// ==/UserScript==
//Ctrl+Alt+Shift+I to open network tab on addon
var gpxParser = function () {
this.xmlSource = "";
this.metadata = {};
this.waypoints = [];
this.tracks = [];
this.routes = [];
};
gpxParser.prototype.parse = function (gpxstring) {
var keepThis = this;
var domParser = new window.DOMParser();
this.xmlSource = domParser.parseFromString(gpxstring, 'text/xml');
metadata = this.xmlSource.querySelector('metadata');
if(metadata != null){
this.metadata.name = this.getElementValue(metadata, "name");
this.metadata.desc = this.getElementValue(metadata, "desc");
this.metadata.time = this.getElementValue(metadata, "time");
let author = {};
let authorElem = metadata.querySelector('author');
if(authorElem != null){
author.name = this.getElementValue(authorElem, "name");
author.email = {};
let emailElem = authorElem.querySelector('email');
if(emailElem != null){
author.email.id = emailElem.getAttribute("id");
author.email.domain = emailElem.getAttribute("domain");
}
let link = {};
let linkElem = authorElem.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = this.getElementValue(linkElem, "text");
link.type = this.getElementValue(linkElem, "type");
}
author.link = link;
}
this.metadata.author = author;
let link = {};
let linkElem = this.queryDirectSelector(metadata, 'link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = this.getElementValue(linkElem, "text");
link.type = this.getElementValue(linkElem, "type");
this.metadata.link = link;
}
}
var wpts = [].slice.call(this.xmlSource.querySelectorAll('wpt'));
for (let idx in wpts){
var wpt = wpts[idx];
let pt = {};
pt.name = keepThis.getElementValue(wpt, "name")
pt.lat = parseFloat(wpt.getAttribute("lat"));
pt.lon = parseFloat(wpt.getAttribute("lon"));
pt.ele = parseFloat(keepThis.getElementValue(wpt, "ele")) || null;
pt.cmt = keepThis.getElementValue(wpt, "cmt");
pt.desc = keepThis.getElementValue(wpt, "desc");
pt.sym = keepThis.getElementValue(wpt, "sym");
keepThis.waypoints.push(pt);
}
var rtes = [].slice.call(this.xmlSource.querySelectorAll('rte'));
for (let idx in rtes){
var rte = rtes[idx];
let route = {};
route.name = keepThis.getElementValue(rte, "name");
route.cmt = keepThis.getElementValue(rte, "cmt");
route.desc = keepThis.getElementValue(rte, "desc");
route.src = keepThis.getElementValue(rte, "src");
route.number = keepThis.getElementValue(rte, "number");
let type = keepThis.queryDirectSelector(rte, "type");
route.type = type != null ? type.innerHTML : null;
let link = {};
let linkElem = rte.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = keepThis.getElementValue(linkElem, "text");
link.type = keepThis.getElementValue(linkElem, "type");
}
route.link = link;
let routepoints = [];
var rtepts = [].slice.call(rte.querySelectorAll('rtept'));
for (let idxIn in rtepts){
var rtept = rtepts[idxIn];
let pt = {};
pt.lat = parseFloat(rtept.getAttribute("lat"));
pt.lon = parseFloat(rtept.getAttribute("lon"));
pt.ele = parseFloat(keepThis.getElementValue(rtept, "ele"));
routepoints.push(pt);
}
route.points = routepoints;
keepThis.routes.push(route);
}
var trks = [].slice.call(this.xmlSource.querySelectorAll('trk'));
for (let idx in trks){
var trk = trks[idx];
let track = {};
track.name = keepThis.getElementValue(trk, "name");
track.cmt = keepThis.getElementValue(trk, "cmt");
track.desc = keepThis.getElementValue(trk, "desc");
track.src = keepThis.getElementValue(trk, "src");
track.number = keepThis.getElementValue(trk, "number");
track.color = keepThis.getElementValue(trk, "DisplayColor");
let type = keepThis.queryDirectSelector(trk, "type");
let link = {};
let linkElem = trk.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = keepThis.getElementValue(linkElem, "text");
link.type = keepThis.getElementValue(linkElem, "type");
}
track.link = link;
let trackpoints = [];
var trkpts = [].slice.call(trk.querySelectorAll('trkpt'));
for (let idxIn in trkpts){
var trkpt = trkpts[idxIn];
let pt = {};
pt.lat = parseFloat(trkpt.getAttribute("lat"));
pt.lon = parseFloat(trkpt.getAttribute("lon"));
pt.ele = parseFloat(keepThis.getElementValue(trkpt, "ele")) || null;
trackpoints.push(pt);
}
track.points = trackpoints;
keepThis.tracks.push(track);
}
};
gpxParser.prototype.getElementValue = function(parent, needle){
let elem = parent.querySelector(needle);
if(elem != null){
return $('<div>').html(elem.innerHTML != undefined ? elem.innerHTML : elem.childNodes[0].data).text();
}
return elem;
}
gpxParser.prototype.queryDirectSelector = function(parent, needle) {
let elements = parent.querySelectorAll(needle);
let finalElem = elements[0];
if(elements.length > 1) {
let directChilds = parent.childNodes;
for(idx in directChilds) {
elem = directChilds[idx];
if(elem.tagName === needle) {
finalElem = elem;
}
}
}
return finalElem;
}
/*
gpxParser.prototype.toGeoJSON = function () {
var GeoJSON = {
"type": "FeatureCollection",
"features": [],
"properties": {
"name": this.metadata.name,
"desc": this.metadata.desc,
"time": this.metadata.time,
"author": this.metadata.author,
"link": this.metadata.link,
},
};
for(idx in this.tracks) {
let track = this.tracks[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = track.name;
feature.properties.cmt = track.cmt;
feature.properties.desc = track.desc;
feature.properties.src = track.src;
feature.properties.number = track.number;
feature.properties.link = track.link;
feature.properties.type = track.type;
for(idx in track.points) {
let pt = track.points[idx];
var geoPt = [];
geoPt.push(pt.lon);
geoPt.push(pt.lat);
geoPt.push(pt.ele);
feature.geometry.coordinates.push(geoPt);
}
GeoJSON.features.push(feature);
}
for(idx in this.routes) {
let track = this.routes[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = track.name;
feature.properties.cmt = track.cmt;
feature.properties.desc = track.desc;
feature.properties.src = track.src;
feature.properties.number = track.number;
feature.properties.link = track.link;
feature.properties.type = track.type;
for(idx in track.points) {
let pt = track.points[idx];
var geoPt = [];
geoPt.push(pt.lon);
geoPt.push(pt.lat);
geoPt.push(pt.ele);
feature.geometry.coordinates.push(geoPt);
}
GeoJSON.features.push(feature);
}
for(idx in this.waypoints) {
let pt = this.waypoints[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = pt.name;
feature.properties.cmt = pt.cmt;
feature.properties.desc = pt.desc;
feature.geometry.coordinates = [pt.lon, pt.lat, pt.ele];
GeoJSON.features.push(feature);
}
return GeoJSON;
}
*/
gpxParser.prototype.getGPX = function(sName) {
var sTrack = '';
for(var i=0 ; i < this.tracks.length ; i++) {
var sTrkSeg = '';
for(var j=0 ; j < this.tracks[i].points.length ; j++) {
sTrkSeg += '<trkpt lat="'+this.tracks[i].points[j].lat+'" lon="'+this.tracks[i].points[j].lon+'">'+
'<ele>'+(this.tracks[i].points[j].ele || '')+'</ele>'+
'</trkpt>';
}
sTrack += '<trk>'+
'<name>'+(this.tracks[i].name || '')+'</name>'+
'<desc>'+(this.tracks[i].desc || '')+'</desc>'+
'<src>'+(this.tracks[i].src || '')+'</src>'+
'<trkseg>'+sTrkSeg+'</trkseg>'+
'</trk>';
}
var sGPX = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'+
'<gpx version="1.1" creator="Franzz" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'+
'<metadata><name>'+sName+'</name></metadata>'+
sTrack+
'</gpx>';
return sGPX;
};
class Gaia {
static get URL() { return 'https://www.gaiagps.com'; }
static get API() { return Gaia.URL+'/api/objects'; }
constructor($InputFile, $InputButton) {
this.asFiles = [];
this.aoWaypoints = [];
this.aoTracks = [];
this.sFolderId = '';
this.asFolder = {};
this.sFolderName = '';
this.$Feedback = $('<div>', {'style':'position:fixed;width:100%;height:25%;bottom:0;background:white;z-index:10000;border-top:1px solid #CCC;box-sizing:border-box;font-size:1.2em;overflow:auto;padding:0 1em;'});
this.$InputName = $('<input>', {'type':'text', 'name':'folder_name', 'placeholder':'Folder Name', 'style': 'border-width:2px; border-color:rgba(0, 0, 0, 0.1); border-radius:4px; font-family:Inter,Helvetica Neue,Helvetica,Arial; font-size:15px; padding:8px 12px; margin-bottom:12px;'});
this.$InputBox = $InputButton.parent();
//Reboot object to remove events
this.$InputFile = $InputFile.clone().insertAfter($InputFile);
$InputFile.remove();
this.$InputButton = $InputButton.clone().insertAfter($InputButton);
$InputButton.remove();
this.setLayout();
}
feedback(sType, sMsg) {
var sFormattedMsg = sType.charAt(0).toUpperCase()+sType.slice(1)+': '+sMsg+(sMsg.slice(-1)=='.'?'':'.');
console.log(sFormattedMsg);
let sColor = 'black';
switch(sType) {
case 'error': sColor = 'red'; break;
case 'warning': sColor = 'orange'; break;
case 'info': sColor = 'green'; break;
}
this.$Feedback.append($('<p>', {'style': 'color: '+sColor+';'}).text(sFormattedMsg));
this.$Feedback.scrollTop(this.$Feedback.prop("scrollHeight"));
}
//Modify Gaia DOM Interface
setLayout() {
//Add Feedback box
this.$Feedback.appendTo($('body'));
//Add Event on button
this.$InputButton.click(() => {this.$InputFile.click();});
//Set event on file selection
this.$InputFile
.attr('multiple', 'multiple')
.attr('name', 'files[]')
.change(() => {this.readInputFiles();});
//Set default Name
this.$InputName.val('PCT').prependTo(this.$InputBox);
//Clear all upload notifications
this.resetNotif();
}
//Marking all upload notifications as read
resetNotif() {
this.feedback('info', 'Marking all upload notifications as read');
$.get(Gaia.URL+'/social/notifications/popup/').done((asNotifs) => {
for(var i in asNotifs) {
if(!asNotifs[i].isViewed && asNotifs[i].html.indexOf('has completed') != -1) {
$.post(Gaia.URL+'/social/notifications/'+asNotifs[i].id+'/markviewed/');
}
}
});
}
//Parse files from input (multiple)
readInputFiles() {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
this.feedback('error', 'File APIs are not fully supported in this browser');
return;
}
else this.feedback('info', 'Parsing input files');
//Get Folder Name (text input)
this.sFolderName = this.$InputName.val();
let aoReaders = [];
let iCount = 0;
this.asFiles = this.$InputFile.prop('files');
for(var i=0 ; i < this.asFiles.length ; i++) {
aoReaders[i] = new FileReader();
aoReaders[i].onload = ((oFileReader) => {
return (asResult) => {
iCount++;
this.feedback('info', 'Reading file "'+oFileReader.name+'" ('+iCount+'/'+this.asFiles.length+')');
this.parseFile(oFileReader.name, asResult.target.result);
if(iCount == this.asFiles.length) this.createFolder();
};
})(this.asFiles[i]);
aoReaders[i].readAsText(this.asFiles[i]);
}
}
//Parse GPX files to consolidate tracks & waypoints
parseFile(sFileName, oContent) {
this.feedback('info', 'Parsing file "'+sFileName+'"');
var oGPX = new gpxParser();
oGPX.parse(oContent);
//Waypoints
for(var w in oGPX.waypoints) {
oGPX.waypoints[w].filename = sFileName;
var sWaypointName = oGPX.waypoints[w].name;
if(sWaypointName.indexOf('Milestone - ') != -1 && sWaypointName.substr(-2) != '00') { //1 milestone every 100 miles
this.feedback('info', 'Ignoring milestone waypoint "'+sWaypointName+'"');
}
else this.aoWaypoints.push(oGPX.waypoints[w]);
}
//Tracks
for(var t in oGPX.tracks) {
oGPX.tracks[t].filename = sFileName;
this.aoTracks.push(oGPX.tracks[t]);
}
}
//Delete existing folder with same name & recreating it
createFolder() {
this.feedback('info', 'Looking for existing folder "'+this.sFolderName+'"...');
$.get(Gaia.API+'/folder/?search='+this.sFolderName).done((asFolders) => {
if(asFolders != '' && !$.isEmptyObject(asFolders)) {
for(var f in asFolders) {
this.feedback('info', 'Deleting "'+asFolders[f].title+'" folder');
$.ajax({
url: Gaia.API+'/folder/'+asFolders[f].id+'/',
type: 'DELETE'
});
}
}
else this.feedback('info', 'No folder named "'+this.sFolderName+'" found');
this.feedback('info', 'Creating folder "'+this.sFolderName+'"');
$.post({
url: Gaia.API+'/folder/',
contentType: 'application/json',
data: JSON.stringify({imported: true, title: this.sFolderName})
}).done((asFolder) => {
this.feedback('info', 'Folder "'+asFolder.properties.name+'" created');
this.asFolder = asFolder;
this.sFolderId = asFolder.id;
this.sFolderName = asFolder.properties.name;
this.uploadTrack();
});
});
}
//Build & Upload Track File
uploadTrack() {
this.feedback('info', 'Uploading tracks...');
//Convert to geojson
let iPostedTracks = 0;
$.each(this.aoTracks, (iIndex, aoTrack) => {
//Set color
let sColor = '#4ABD32';
switch(aoTrack.color) {
case 'DarkBlue': sColor = '#2D3FC7'; break;
case 'Magenta': sColor = '#B60DC3'; break;
}
//Add track points
let aoCoords = [];
for(var p in aoTrack.points) {
let pt = aoTrack.points[p];
aoCoords.push([pt.lon, pt.lat, pt.ele, 0]);
}
//Upload
let sPostedData = JSON.stringify({
type: 'feature',
properties: {
color: sColor,
title: aoTrack.name,
time_created: '2020-12-27T11:34:03.537Z',
routing_mode: null,
notes: aoTrack.desc,
//distance: 168405.62350073704,
isValid: true,
isLatestImport: true,
filename: aoTrack.filename,
localId: 'track'+(iIndex + 1),
writable: true,
archived: false,
isPublicTrack: false,
isLocallyCreated: true,
type: 'track',
parent_folder_id: this.sFolderId,
hexcolor: sColor
},
geometry: {
type: 'MultiLineString',
coordinates: [aoCoords]
}
});
var self = this;
this.feedback('info', 'Uploading track "'+aoTrack.name+'"');
$.post({
url: Gaia.API+'/track/',
contentType: 'application/json',
data: sPostedData,
postedData: sPostedData,
trackName: aoTrack.name
}).done(function(asTrack) {
self.aoTracks[iIndex] = asTrack;
$.ajax({
url: Gaia.API+'/track/'+asTrack.features[0].id+'/',
type: 'PUT',
contentType: 'application/json',
data: this.postedData,
trackName: this.trackName
}).done(function() {
iPostedTracks++;
self.feedback('info', 'Track "'+this.trackName+'" uploaded');
if(iPostedTracks == self.aoTracks.length) {
self.feedback('info', 'All tracks uploaded');
self.uploadWayPoints();
}
}).fail(function() {
self.feedback('error', 'Track "'+this.trackName+'" failed to upload');
});
});
});
/* Legacy lethod: through form submit
//Build track file
this.feedback('info', 'Building consolidated track');
var oGPX = new gpxParser();
oGPX.tracks = this.aoTracks;
var sTrack = oGPX.getGPX(this.sFolderName);
//Send consolidated Tracks as one file
var oForm = new FormData();
oForm.append('name', this.sFolderName);
oForm.append('files', new Blob([sTrack]), 'Consolidated.gpx');
this.feedback('info', 'Uploading consolidated track');
$.ajax({
url: Gaia.URL+'/upload/',
type: 'POST',
data: oForm,
enctype: 'multipart/form-data',
processData: false,
contentType: false,
cache: false
}).done(() => {this.checkNotif();});
*/
}
/*
//Wait for file to be processed by Gaia
checkNotif() {
this.feedback('info', 'Waiting for Gaia to process consolidated track');
$.get(Gaia.URL+'/social/notifications/popup/').done((asNotifs) => {
for(var i in asNotifs) {
if(!asNotifs[i].isViewed && asNotifs[i].html.indexOf('has completed') != -1) {
this.feedback('info', 'Notification '+asNotifs[i].id+' found. Marking as read');
var $Notif = $('<span>').html(asNotifs[i].html);
this.sFolderId = $Notif.find('a').attr('href').split('/')[3];
$.post(Gaia.URL+'/social/notifications/'+asNotifs[i].id+'/markviewed/');
}
}
if(this.sFolderId != '') {
this.setTracksColor();
this.uploadWayPoints();
}
else setTimeout((() => {this.checkNotif();}), 1000);
});
}
//Convert QMapshack Track Colors into Gaia Colors
setTracksColor() {
this.feedback('info', 'Track Color - Matching Gaia track IDs with GPX tracks');
var iCount = 0;
$.get(Gaia.API+'/folder/'+this.sFolderId+'/').done((asFolder) => {
var self = this;
for(var iGaiaIndex in asFolder.properties.tracks) {
for(var iGpxIndex in this.aoTracks) {
if(asFolder.properties.tracks[iGaiaIndex].title == this.aoTracks[iGpxIndex].name) {
this.aoTracks[iGpxIndex].id = asFolder.properties.tracks[iGaiaIndex].id;
this.feedback('info', 'Track Color - Track "'+this.aoTracks[iGpxIndex].name+'" found a match ('+(++iCount)+'/'+this.aoTracks.length+')');
$.ajax({
url: Gaia.API+'/track/'+this.aoTracks[iGpxIndex].id+'/',
color: this.aoTracks[iGpxIndex].color
}).done(function(asGaiaTrackDisplay) {
var asGaiaTrack = asGaiaTrackDisplay.features[0];
delete asGaiaTrack.id;
delete asGaiaTrack.style;
//Set color
var sColor = '#ff0000';
switch(this.color) {
case 'DarkBlue': sColor = '#2D3FC7'; break;
case 'Magenta': sColor = '#B60DC3'; break;
}
asGaiaTrack.properties.color = sColor;
asGaiaTrack.properties.hexcolor = sColor;
self.feedback('info', 'Track Color - Setting track color "'+sColor+'" ('+this.color+') to "'+asGaiaTrack.properties.title+'"');
$.ajax({
url: Gaia.API+'/track/'+asGaiaTrack.properties.id+'/',
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify(asGaiaTrack)
});
});
}
}
}
});
}
*/
uploadWayPoints(iIndex, bSecondTry) {
iIndex = iIndex || 0;
bSecondTry = bSecondTry || false;
//Upload waypoints
if(iIndex < this.aoWaypoints.length) {
var sWaypointName = this.aoWaypoints[iIndex].name;
this.feedback('info', 'Uploading waypoint '+(iIndex + 1)+'/'+this.aoWaypoints.length+' ('+sWaypointName+')');
var asPost = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
this.aoWaypoints[iIndex].lon,
this.aoWaypoints[iIndex].lat,
this.aoWaypoints[iIndex].ele
]
},
properties: {
title: sWaypointName,
time_created: "2020-06-07T14:01:03.944Z",
icon: Gaia.getIconName(this.aoWaypoints[iIndex].sym),
isValid: true,
isLatestImport: true,
filename: this.aoWaypoints[iIndex].filename,
writable: true,
localId: iIndex+'',
notes: this.aoWaypoints[iIndex].desc,
archived: false,
isLocallyCreated: true,
type: 'waypoint',
parent_folder_id: this.sFolderId
}
};
let sData = JSON.stringify(asPost);
var self = this;
$.post({
url: Gaia.API+'/waypoint/',
contentType: 'application/json',
data: sData,
postedData: sData
}).done(function(asWaypoint) {
//Update local waypoint with all server info (including ID)
self.aoWaypoints[iIndex] = asWaypoint;
$.ajax({
url: Gaia.API+'/waypoint/'+asWaypoint.properties.id+'/',
type: 'PUT',
contentType: 'application/json',
data: this.postedData
}).always(() => {
self.uploadWayPoints(++iIndex);
});
}).fail(function(){
self.feedback('error', 'Failed to upload waypoint #'+(iIndex + 1)+' ('+sWaypointName+'). Trying again...');
self.uploadWayPoints(iIndex, true);
});
}
//Done uploading, assigning waypoints & tracks to folder
else this.assignElementsToFolder();
}
assignElementsToFolder() {
this.feedback('info', 'Assigning all elements to folder "'+this.asFolder.properties.name+'"');
let asFolder = {
cover_photo_id: this.asFolder.properties.cover_photo_id,
id: this.asFolder.id,
name: this.asFolder.properties.name,
notes: this.asFolder.properties.notes,
time_created: this.asFolder.properties.time_created,
updated_date: this.asFolder.properties.updated_date
}
//Assign waypoints to folder
asFolder.waypoints = [];
for(var w in this.aoWaypoints) asFolder.waypoints.push(this.aoWaypoints[w].properties.id);
//Assign tracks to folder
asFolder.tracks = [];
for(var t in this.aoTracks) asFolder.tracks.push(this.aoTracks[t].features[0].id);
$.ajax({
url: Gaia.API+'/folder/'+asFolder.id+'/',
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify(asFolder)
}).done(() => {
this.feedback('info', 'Done');
}).fail(() => {
this.feedback('error', 'WFailed to assign waypoints & tracks to folder ID "'+asFolder.id+'"');
});
}
static getIconName(sGarminName) {
var asMapping = {
'Trail Head': 'trailhead',
'Water Source': 'water-24.png',
'Truck': 'car-24.png',
'Post Office': 'resupply',
'Flag, Blue': 'blue-pin-down.png',
'Car': 'car-24.png',
'Flag, Red': 'red-pin-down.png',
'Campground': 'campsite-24.png',
'Flag, Green': 'green-pin.png',
'Powerline': 'petroglyph',
'Shopping Center': 'cafe-24.png',
'Lodging': 'lodging-24.png',
'Water Hydrant': 'red-pin-down.png',
'Drinking Water': 'potable-water',
'Toll Booth': 'ranger-station',
'Summit': 'peak',
'Park': 'park-24.png',
'Forest': 'forest',
'Cemetery': 'cemetery-24.png',
'Bridge': 'dam-24.png',
'Restaurant': 'restaurant-24.png',
'Picnic Area': 'picnic',
'Residence': 'city-24.png',
'City (Capitol)': 'city-24.png',
'Ski Resort': 'skiing-24.png',
'Restroom': 'toilets-24.png',
'Ground Transportation': 'car-24.png',
'Church': 'ghost-town'
};
return (sGarminName in asMapping)?asMapping[sGarminName]:'red-pin-down.png';
}
}
console.log('Loading GaiaGps Uploader...');
//To be adjusted regularly
$FileInput = $('input[type=file]')
$FileButton = $('a[href="https://help.gaiagps.com/hc/en-us/articles/360052763513"]').parent().find('button');
let oGaia = new Gaia($FileInput, $FileButton);