this should also prevent mixed release plugins/test IITC builds causing issues with it's 'show more portals' plugin further changes are planned - by lying to the backend about the map zoom to optimise the portal detail level (density) returned and make better use of the data cache. needs some practical experimentation to get good adjustments, and a cleaner API for these plugins
474 lines
15 KiB
JavaScript
474 lines
15 KiB
JavaScript
// UTILS + MISC ///////////////////////////////////////////////////////
|
||
|
||
window.aboutIITC = function() {
|
||
var v = (script_info.script && script_info.script.version || script_info.dateTimeVersion) + ' ['+script_info.buildName+']';
|
||
if (typeof android !== 'undefined' && android && android.getVersionName) {
|
||
v += '[IITC Mobile '+android.getVersionName()+']';
|
||
}
|
||
|
||
var plugins = '<ul>';
|
||
for (var i in bootPlugins) {
|
||
var info = bootPlugins[i].info;
|
||
if (info) {
|
||
var pname = info.script && info.script.name || info.pluginId;
|
||
if (pname.substr(0,13) == 'IITC plugin: ' || pname.substr(0,13) == 'IITC Plugin: ') {
|
||
pname = pname.substr(13);
|
||
}
|
||
var pvers = info.script && info.script.version || info.dateTimeVersion;
|
||
|
||
var ptext = pname + ' - ' + pvers;
|
||
if (info.buildName != script_info.buildName) {
|
||
ptext += ' ['+(info.buildName||'<i>non-standard plugin</i>')+']';
|
||
}
|
||
|
||
plugins += '<li>'+ptext+'</li>';
|
||
} else {
|
||
// no 'info' property of the plugin setup function - old plugin wrapper code
|
||
// could attempt to find the "window.plugin.NAME = function() {};" line it's likely to have..?
|
||
plugins += '<li>(unknown plugin: index '+i+')</li>';
|
||
}
|
||
}
|
||
plugins += '</ul>';
|
||
|
||
var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
|
||
var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
|
||
|
||
var a = ''
|
||
+ ' <div><b>About IITC</b></div> '
|
||
+ ' <div>Ingress Intel Total Conversion</div> '
|
||
+ ' <hr>'
|
||
+ ' <div>'
|
||
+ ' <a href="http://iitc.jonatkins.com/" target="_blank">IITC Homepage</a><br />'
|
||
+ ' On the script’s homepage you can:'
|
||
+ ' <ul>'
|
||
+ ' <li>Find Updates</li>'
|
||
+ ' <li>Get Plugins</li>'
|
||
+ ' <li>Report Bugs</li>'
|
||
+ ' <li>Contribute!</li>'
|
||
+ ' </ul>'
|
||
+ ' </div>'
|
||
+ ' <div>'
|
||
+ ' MapQuest OSM tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="https://developer.mapquest.com/content/osm/mq_logo.png">'
|
||
+ ' </div>'
|
||
+ ' <hr>'
|
||
+ ' <div>Version: ' + v + '</div>'
|
||
+ ' <div>Plugins: ' + plugins + '</div>'
|
||
+ ' <hr>'
|
||
+ ' <div>' + attrib + '</div>'
|
||
+ ' <hr>'
|
||
+ ' <div>' + contrib + '</div>';
|
||
|
||
dialog({
|
||
title: 'IITC ' + v,
|
||
html: a,
|
||
dialogClass: 'ui-dialog-aboutIITC'
|
||
});
|
||
}
|
||
|
||
|
||
window.layerGroupLength = function(layerGroup) {
|
||
var layersCount = 0;
|
||
var layers = layerGroup._layers;
|
||
if (layers)
|
||
layersCount = Object.keys(layers).length;
|
||
return layersCount;
|
||
}
|
||
|
||
// retrieves parameter from the URL?query=string.
|
||
window.getURLParam = function(param) {
|
||
var v = document.URL;
|
||
var i = v.indexOf(param);
|
||
if(i <= -1) return '';
|
||
v = v.substr(i);
|
||
i = v.indexOf("&");
|
||
if(i >= 0) v = v.substr(0, i);
|
||
return v.replace(param+"=","");
|
||
}
|
||
|
||
// read cookie by name.
|
||
// http://stackoverflow.com/a/5639455/1684530 by cwolves
|
||
var cookies;
|
||
window.readCookie = function(name,c,C,i){
|
||
if(cookies) return cookies[name];
|
||
c = document.cookie.split('; ');
|
||
cookies = {};
|
||
for(i=c.length-1; i>=0; i--){
|
||
C = c[i].split('=');
|
||
cookies[C[0]] = unescape(C[1]);
|
||
}
|
||
return cookies[name];
|
||
}
|
||
|
||
window.writeCookie = function(name, val) {
|
||
document.cookie = name + "=" + val + '; expires=Thu, 31 Dec 2020 23:59:59 GMT; path=/';
|
||
}
|
||
|
||
window.eraseCookie = function(name) {
|
||
document.cookie = name + '=; expires=Thu, 1 Jan 1970 00:00:00 GMT; path=/';
|
||
}
|
||
|
||
//certain values were stored in cookies, but we're better off using localStorage instead - make it easy to convert
|
||
window.convertCookieToLocalStorage = function(name) {
|
||
var cookie=readCookie(name);
|
||
if(cookie !== undefined) {
|
||
console.log('converting cookie '+name+' to localStorage');
|
||
if(localStorage[name] === undefined) {
|
||
localStorage[name] = cookie;
|
||
}
|
||
eraseCookie(name);
|
||
}
|
||
}
|
||
|
||
// add thousand separators to given number.
|
||
// http://stackoverflow.com/a/1990590/1684530 by Doug Neiner.
|
||
window.digits = function(d) {
|
||
// U+2009 - Thin Space. Recommended for use as a thousands separator...
|
||
// https://en.wikipedia.org/wiki/Space_(punctuation)#Table_of_spaces
|
||
return (d+"").replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1 ");
|
||
}
|
||
|
||
|
||
|
||
// posts AJAX request to Ingress API.
|
||
// action: last part of the actual URL, the rpc/dashboard. is
|
||
// added automatically
|
||
// data: JSON data to post. method will be derived automatically from
|
||
// action, but may be overridden. Expects to be given Hash.
|
||
// Strings are not supported.
|
||
// success: method to call on success. See jQuery API docs for avail-
|
||
// able arguments: http://api.jquery.com/jQuery.ajax/
|
||
// error: see above. Additionally it is logged if the request failed.
|
||
window.postAjax = function(action, data, success, error) {
|
||
|
||
var methodName = 'dashboard.'+action;
|
||
var versionStr = 'version_parameter';
|
||
|
||
// munging of the method name - seen in Set 2 onwards
|
||
methodName = mungeOneString(methodName);
|
||
// and of the 'version' parameter (we assume it's a version - if missing/wrong that's what the error refers to)
|
||
versionStr = mungeOneString(versionStr);
|
||
|
||
var post_data = JSON.stringify(window.requestDataMunge($.extend({}, data, {method: methodName, version: versionStr})));
|
||
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
|
||
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
|
||
var result = $.ajax({
|
||
url: '/r/'+methodName,
|
||
type: 'POST',
|
||
data: post_data,
|
||
context: data,
|
||
dataType: 'json',
|
||
success: [remove, success],
|
||
error: error ? [errCnt, error] : errCnt,
|
||
contentType: 'application/json; charset=utf-8',
|
||
beforeSend: function(req) {
|
||
req.setRequestHeader('X-CSRFToken', readCookie('csrftoken'));
|
||
}
|
||
});
|
||
result.action = action;
|
||
return result;
|
||
}
|
||
|
||
window.zeroPad = function(number,pad) {
|
||
number = number.toString();
|
||
var zeros = pad - number.length;
|
||
return Array(zeros>0?zeros+1:0).join("0") + number;
|
||
}
|
||
|
||
|
||
// converts javascript timestamps to HH:mm:ss format if it was today;
|
||
// otherwise it returns YYYY-MM-DD
|
||
window.unixTimeToString = function(time, full) {
|
||
if(!time) return null;
|
||
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
|
||
var time = d.toLocaleTimeString();
|
||
// var time = zeroPad(d.getHours(),2)+':'+zeroPad(d.getMinutes(),2)+':'+zeroPad(d.getSeconds(),2);
|
||
var date = d.getFullYear()+'-'+zeroPad(d.getMonth()+1,2)+'-'+zeroPad(d.getDate(),2);
|
||
if(typeof full !== 'undefined' && full) return date + ' ' + time;
|
||
if(d.toDateString() == new Date().toDateString())
|
||
return time;
|
||
else
|
||
return date;
|
||
}
|
||
|
||
// converts a javascript time to a precise date and time (optionally with millisecond precision)
|
||
// formatted in ISO-style YYYY-MM-DD hh:mm:ss.mmm - but using local timezone
|
||
window.unixTimeToDateTimeString = function(time, millisecond) {
|
||
if(!time) return null;
|
||
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
|
||
return d.getFullYear()+'-'+zeroPad(d.getMonth()+1,2)+'-'+zeroPad(d.getDate(),2)
|
||
+' '+zeroPad(d.getHours(),2)+':'+zeroPad(d.getMinutes(),2)+':'+zeroPad(d.getSeconds(),2)+(millisecond?'.'+zeroPad(d.getMilliseconds(),3):'');
|
||
}
|
||
|
||
window.unixTimeToHHmm = function(time) {
|
||
if(!time) return null;
|
||
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
|
||
var h = '' + d.getHours(); h = h.length === 1 ? '0' + h : h;
|
||
var s = '' + d.getMinutes(); s = s.length === 1 ? '0' + s : s;
|
||
return h + ':' + s;
|
||
}
|
||
|
||
window.formatInterval = function(seconds,maxTerms) {
|
||
|
||
var d = Math.floor(seconds / 86400);
|
||
var h = Math.floor((seconds % 86400) / 3600);
|
||
var m = Math.floor((seconds % 3600) / 60);
|
||
var s = seconds % 60;
|
||
|
||
var terms = [];
|
||
if (d > 0) terms.push(d+'d');
|
||
if (h > 0) terms.push(h+'h');
|
||
if (m > 0) terms.push(m+'m');
|
||
if (s > 0 || terms.length==0) terms.push(s+'s');
|
||
|
||
if (maxTerms) terms = terms.slice(0,maxTerms);
|
||
|
||
return terms.join(' ');
|
||
}
|
||
|
||
|
||
window.rangeLinkClick = function() {
|
||
if(window.portalRangeIndicator)
|
||
window.map.fitBounds(window.portalRangeIndicator.getBounds());
|
||
if(window.isSmartphone())
|
||
window.show('map');
|
||
}
|
||
|
||
window.showPortalPosLinks = function(lat, lng, name) {
|
||
var encoded_name = 'undefined';
|
||
if(name !== undefined) {
|
||
encoded_name = encodeURIComponent(name);
|
||
}
|
||
|
||
if (typeof android !== 'undefined' && android && android.intentPosLink) {
|
||
android.intentPosLink(lat, lng, map.getZoom(), name, true);
|
||
} else {
|
||
var qrcode = '<div id="qrcode"></div>';
|
||
var script = '<script>$(\'#qrcode\').qrcode({text:\'GEO:'+lat+','+lng+'\'});</script>';
|
||
var gmaps = '<a href="https://maps.google.com/?q='+lat+','+lng+'%20('+encoded_name+')">Google Maps</a>';
|
||
var bingmaps = '<a href="http://www.bing.com/maps/?v=2&cp='+lat+'~'+lng+'&lvl=16&sp=Point.'+lat+'_'+lng+'_'+encoded_name+'___">Bing Maps</a>';
|
||
var osm = '<a href="http://www.openstreetmap.org/?mlat='+lat+'&mlon='+lng+'&zoom=16">OpenStreetMap</a>';
|
||
var latLng = '<span><' + lat + ',' + lng +'></span>';
|
||
dialog({
|
||
html: '<div style="text-align: center;">' + qrcode + script + gmaps + '; ' + bingmaps + '; ' + osm + '<br />' + latLng + '</div>',
|
||
title: name,
|
||
id: 'poslinks'
|
||
});
|
||
}
|
||
}
|
||
|
||
window.isTouchDevice = function() {
|
||
return 'ontouchstart' in window // works on most browsers
|
||
|| 'onmsgesturechange' in window; // works on ie10
|
||
};
|
||
|
||
window.androidCopy = function(text) {
|
||
if(typeof android === 'undefined' || !android || !android.copy)
|
||
return true; // i.e. execute other actions
|
||
else
|
||
android.copy(text);
|
||
return false;
|
||
}
|
||
|
||
window.androidPermalink = function() {
|
||
if(typeof android === 'undefined' || !android || !android.intentPosLink)
|
||
return true; // i.e. execute other actions
|
||
|
||
var center = map.getCenter();
|
||
android.intentPosLink(center.lat, center.lng, map.getZoom(), "Selected map view", false);
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
window.getMinPortalLevel = function() {
|
||
var z = map.getZoom();
|
||
return getMapZoomTileParameters(z).level;
|
||
}
|
||
|
||
// returns number of pixels left to scroll down before reaching the
|
||
// bottom. Works similar to the native scrollTop function.
|
||
window.scrollBottom = function(elm) {
|
||
if(typeof elm === 'string') elm = $(elm);
|
||
return elm.get(0).scrollHeight - elm.innerHeight() - elm.scrollTop();
|
||
}
|
||
|
||
window.zoomToAndShowPortal = function(guid, latlng) {
|
||
map.setView(latlng, 17);
|
||
// if the data is available, render it immediately. Otherwise defer
|
||
// until it becomes available.
|
||
if(window.portals[guid])
|
||
renderPortalDetails(guid);
|
||
else
|
||
urlPortal = guid;
|
||
}
|
||
|
||
|
||
String.prototype.capitalize = function() {
|
||
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
|
||
}
|
||
|
||
// http://stackoverflow.com/a/646643/1684530 by Bergi and CMS
|
||
if (typeof String.prototype.startsWith !== 'function') {
|
||
String.prototype.startsWith = function (str){
|
||
return this.slice(0, str.length) === str;
|
||
};
|
||
}
|
||
|
||
// escape a javascript string, so quotes and backslashes are escaped with a backslash
|
||
// (for strings passed as parameters to html onclick="..." for example)
|
||
window.escapeJavascriptString = function(str) {
|
||
return (str+'').replace(/[\\"']/g,'\\$&');
|
||
}
|
||
|
||
//escape special characters, such as tags
|
||
window.escapeHtmlSpecialChars = function(str) {
|
||
var div = document.createElement(div);
|
||
var text = document.createTextNode(str);
|
||
div.appendChild(text);
|
||
return div.innerHTML;
|
||
}
|
||
|
||
window.prettyEnergy = function(nrg) {
|
||
return nrg> 1000 ? Math.round(nrg/1000) + ' k': nrg;
|
||
}
|
||
|
||
window.setPermaLink = function(elm) {
|
||
var c = map.getCenter();
|
||
var lat = Math.round(c.lat*1E6)/1E6;
|
||
var lng = Math.round(c.lng*1E6)/1E6;
|
||
var qry = 'll='+lat+','+lng+'&z=' + map.getZoom();
|
||
$(elm).attr('href', '/intel?' + qry);
|
||
}
|
||
|
||
window.uniqueArray = function(arr) {
|
||
return $.grep(arr, function(v, i) {
|
||
return $.inArray(v, arr) === i;
|
||
});
|
||
}
|
||
|
||
window.genFourColumnTable = function(blocks) {
|
||
var t = $.map(blocks, function(detail, index) {
|
||
if(!detail) return '';
|
||
if(index % 2 === 0)
|
||
return '<tr><td>'+detail[1]+'</td><th>'+detail[0]+'</th>';
|
||
else
|
||
return ' <th>'+detail[0]+'</th><td>'+detail[1]+'</td></tr>';
|
||
}).join('');
|
||
if(t.length % 2 === 1) t + '<td></td><td></td></tr>';
|
||
return t;
|
||
}
|
||
|
||
|
||
// converts given text with newlines (\n) and tabs (\t) to a HTML
|
||
// table automatically.
|
||
window.convertTextToTableMagic = function(text) {
|
||
// check if it should be converted to a table
|
||
if(!text.match(/\t/)) return text.replace(/\n/g, '<br>');
|
||
|
||
var data = [];
|
||
var columnCount = 0;
|
||
|
||
// parse data
|
||
var rows = text.split('\n');
|
||
$.each(rows, function(i, row) {
|
||
data[i] = row.split('\t');
|
||
if(data[i].length > columnCount) columnCount = data[i].length;
|
||
});
|
||
|
||
// build the table
|
||
var table = '<table>';
|
||
$.each(data, function(i, row) {
|
||
table += '<tr>';
|
||
$.each(data[i], function(k, cell) {
|
||
var attributes = '';
|
||
if(k === 0 && data[i].length < columnCount) {
|
||
attributes = ' colspan="'+(columnCount - data[i].length + 1)+'"';
|
||
}
|
||
table += '<td'+attributes+'>'+cell+'</td>';
|
||
});
|
||
table += '</tr>';
|
||
});
|
||
table += '</table>';
|
||
return table;
|
||
}
|
||
|
||
// Given 3 sets of points in an array[3]{lat, lng} returns the area of the triangle
|
||
window.calcTriArea = function(p) {
|
||
return Math.abs((p[0].lat*(p[1].lng-p[2].lng)+p[1].lat*(p[2].lng-p[0].lng)+p[2].lat*(p[0].lng-p[1].lng))/2);
|
||
}
|
||
|
||
// Update layerGroups display status to window.overlayStatus and localStorage 'ingress.intelmap.layergroupdisplayed'
|
||
window.updateDisplayedLayerGroup = function(name, display) {
|
||
overlayStatus[name] = display;
|
||
localStorage['ingress.intelmap.layergroupdisplayed'] = JSON.stringify(overlayStatus);
|
||
}
|
||
|
||
// Read layerGroup status from window.overlayStatus if it was added to map,
|
||
// read from cookie if it has not added to map yet.
|
||
// return 'defaultDisplay' if both overlayStatus and cookie didn't have the record
|
||
window.isLayerGroupDisplayed = function(name, defaultDisplay) {
|
||
if(typeof(overlayStatus[name]) !== 'undefined') return overlayStatus[name];
|
||
|
||
convertCookieToLocalStorage('ingress.intelmap.layergroupdisplayed');
|
||
var layersJSON = localStorage['ingress.intelmap.layergroupdisplayed'];
|
||
if(!layersJSON) return defaultDisplay;
|
||
|
||
var layers = JSON.parse(layersJSON);
|
||
// keep latest overlayStatus
|
||
overlayStatus = $.extend(layers, overlayStatus);
|
||
if(typeof(overlayStatus[name]) === 'undefined') return defaultDisplay;
|
||
return overlayStatus[name];
|
||
}
|
||
|
||
window.addLayerGroup = function(name, layerGroup, defaultDisplay) {
|
||
if (defaultDisplay === undefined) defaultDisplay = true;
|
||
|
||
if(isLayerGroupDisplayed(name, defaultDisplay)) map.addLayer(layerGroup);
|
||
layerChooser.addOverlay(layerGroup, name);
|
||
}
|
||
|
||
window.clampLat = function(lat) {
|
||
// the map projection used does not handle above approx +- 85 degrees north/south of the equator
|
||
if (lat > 85.051128)
|
||
lat = 85.051128;
|
||
else if (lat < -85.051128)
|
||
lat = -85.051128;
|
||
return lat;
|
||
}
|
||
|
||
window.clampLng = function(lng) {
|
||
if (lng > 179.999999)
|
||
lng = 179.999999;
|
||
else if (lng < -180.0)
|
||
lng = -180.0;
|
||
return lng;
|
||
}
|
||
|
||
|
||
window.clampLatLng = function(latlng) {
|
||
return new L.LatLng ( clampLat(latlng.lat), clampLng(latlng.lng) );
|
||
}
|
||
|
||
window.clampLatLngBounds = function(bounds) {
|
||
return new L.LatLngBounds ( clampLatLng(bounds.getSouthWest()), clampLatLng(bounds.getNorthEast()) );
|
||
}
|
||
|
||
// avoid error in stock JS
|
||
if(goog && goog.style) {
|
||
goog.style.showElement = function(a, b) {
|
||
if(a && a.style)
|
||
a.style.display = b ? "" : "none"
|
||
};
|
||
}
|
||
|
||
// Fix Leaflet: handle touchcancel events in Draggable
|
||
L.Draggable.prototype._onDownOrig = L.Draggable.prototype._onDown;
|
||
L.Draggable.prototype._onDown = function(e) {
|
||
L.Draggable.prototype._onDownOrig.apply(this, arguments);
|
||
|
||
if(e.type === "touchstart") {
|
||
L.DomEvent.on(document, "touchcancel", this._onUp, this);
|
||
}
|
||
}
|
||
|