diff --git a/code/artifact.js b/code/artifact.js
new file mode 100644
index 00000000..88b899b7
--- /dev/null
+++ b/code/artifact.js
@@ -0,0 +1,185 @@
+// ARTIFACT ///////////////////////////////////////////////////////
+
+// added as part of the ingress #13magnus in november 2013, artifacts
+// are additional game elements overlayed on the intel map
+// currently there are only jarvis-related entities
+// - shards: move between portals (along links) each hour. more than one can be at a portal
+// - targets: specific portals - one per team
+// the artifact data includes details for the specific portals, so can be useful
+
+
+window.artifact = function() {}
+
+window.artifact.setup = function() {
+ artifact.REFRESH_SUCCESS = 15*60; // 15 minutes on success
+ artifact.REFRESH_FAILURE = 2*60; // 2 minute retry on failure
+
+ artifact.idle = false;
+ artifact.clearData();
+
+ addResumeFunction(artifact.idleResume);
+
+ artifact.requestData();
+
+ artifact._layer = new L.LayerGroup();
+ addLayerGroup ('Artifacts (Jarvis shards)', artifact._layer, true);
+}
+
+window.artifact.requestData = function() {
+ if (isIdle()) {
+ artifact.idle = true;
+ } else {
+ window.postAjax('getArtifactInfo', {}, artifact.handleSuccess, artifact.handleError);
+ }
+}
+
+window.artifact.idleResume = function() {
+ if (artifact.idle) {
+ artifact.idle = false;
+ artifact.requestData();
+ }
+}
+
+window.artifact.handleSuccess = function(data) {
+ artifact.processData (data);
+
+ setTimeout (artifact.requestData, artifact.REFRESH_SUCCESS*1000);
+}
+
+window.artifact.handleFailure = function(data) {
+ // no useful data on failure - do nothing
+
+ setTimeout (artifact.requestData, artifact.REFRESH_FAILURE*1000);
+}
+
+
+window.artifact.processData = function(data) {
+
+ if (!data.artifacts) {
+ console.warn('Failed to find artifacts in artifact response');
+ return;
+ }
+
+ artifact.clearData();
+
+ $.each (data.artifacts, function(i,artData) {
+ if (artData.artifactId != 'jarvis') {
+ // jarvis artifacts - fragmentInfos and targetInfos
+ // (future types? completely unknown at this time!)
+ console.warn('Note: unknown artifactId '+artData.artifactId+' - guessing how to handle it');
+ }
+
+ if (artData.fragmentInfos) {
+ artifact.processFragmentInfos (artData.artifactId, artData.fragmentInfos);
+ }
+
+ if (artData.targetInfos) {
+ artifact.processTargetInfos (artData.artifactId, artData.targetInfos);
+ }
+
+ // other data in future? completely unknown!
+ });
+
+
+ // redraw the artifact layer
+ artifact.updateLayer();
+
+}
+
+
+window.artifact.clearData = function() {
+
+ artifact.portalInfo = {};
+}
+
+window.artifact.processFragmentInfos = function (id, fragments) {
+ $.each(fragments, function(i, fragment) {
+ if (!artifact.portalInfo[fragment.portalGuid]) {
+ artifact.portalInfo[fragment.portalGuid] = { _entityData: fragment.portalInfo };
+ }
+
+ if (!artifact.portalInfo[fragment.portalGuid][id]) artifact.portalInfo[fragment.portalGuid][id] = {};
+
+ if (!artifact.portalInfo[fragment.portalGuid][id].fragments) artifact.portalInfo[fragment.portalGuid][id].fragments = [];
+
+ $.each(fragment.fragments, function(i,f) {
+ artifact.portalInfo[fragment.portalGuid][id].fragments.push(f);
+ });
+
+ });
+}
+
+window.artifact.processTargetInfos = function (id, targets) {
+ $.each(targets, function(i, target) {
+ if (!artifact.portalInfo[target.portalGuid]) {
+ artifact.portalInfo[target.portalGuid] = { _entityData: target.portalInfo };
+ }
+
+ if (!artifact.portalInfo[target.portalGuid][id]) artifact.portalInfo[target.portalGuid][id] = {};
+
+ artifact.portalInfo[target.portalGuid][id].target = target.team === 'RESISTANCE' ? TEAM_RES : TEAM_ENL;
+ });
+}
+
+
+// used to render portals that would otherwise be below the visible level
+window.artifact.getArtifactEntities = function() {
+ var entities = [];
+
+ // create fake entities from the artifact data
+ $.each (artifact.portalInfo, function(guid,data) {
+ var timestamp = 0; // we don't have a valid timestamp - so let's use 0
+ var ent = [ guid, timestamp, data._entityData ];
+ entities.push(ent);
+ });
+
+ return entities;
+}
+
+
+window.artifact.updateLayer = function() {
+ artifact._layer.clearLayers();
+
+// TODO: icons
+// //commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard.png
+// //commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard_target_0.png
+// (replace '0' with count of shards at the target portal)
+
+
+ $.each(artifact.portalInfo, function(guid,data) {
+ var latlng = L.latLng ([data._entityData.locationE6.latE6/1E6, data._entityData.locationE6.lngE6/1E6]);
+
+ // jarvis shard icon
+ var iconUrl = undefined;
+ var iconSize = 0;
+
+ if (data.jarvis.fragments) {
+ iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard.png';
+ iconSize = 60/2; // 60 pixels - half that size works better
+ }
+ if (data.jarvis.target) {
+ // target portal - show the target marker. use the count of fragments at the target to pick the right icon - it has segments that fill up
+
+ var count = data.jarvis.fragments ? data.jarvis.fragments.length : 0;
+
+ iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard_target_'+count+'.png';
+ iconSize = 100/2; // 100 pixels - half that size works better
+ }
+
+ if (iconUrl) {
+ var icon = L.icon({
+ iconUrl: iconUrl,
+ iconSize: [iconSize,iconSize],
+ iconAnchor: [iconSize/2,iconSize/2],
+ className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath
+ });
+
+ var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false});
+
+ artifact._layer.addLayer(marker);
+ } else {
+ console.warn('Oops! no URL for artifact portal icon?!');
+ }
+ });
+
+}
diff --git a/code/boot.js b/code/boot.js
index 220e1531..7d39eceb 100644
--- a/code/boot.js
+++ b/code/boot.js
@@ -525,6 +525,7 @@ function boot() {
window.setupLargeImagePreview();
window.setupSidebarToggle();
window.updateGameScore();
+ window.artifact.setup();
window.setupPlayerStat();
window.setupTooltips();
window.chat.setup();
diff --git a/code/map_data_request.js b/code/map_data_request.js
index ae53421e..ecf31368 100644
--- a/code/map_data_request.js
+++ b/code/map_data_request.js
@@ -213,6 +213,7 @@ window.MapDataRequest.prototype.refresh = function() {
this.render.updateEntityVisibility();
+ this.render.processGameEntities(artifact.getArtifactEntities());
console.log('requesting data tiles at zoom '+zoom+' (L'+minPortalLevel+'+ portals), map zoom is '+map.getZoom());
diff --git a/code/utils_misc.js b/code/utils_misc.js
new file mode 100644
index 00000000..2a9643bb
--- /dev/null
+++ b/code/utils_misc.js
@@ -0,0 +1,647 @@
+// 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 = '
';
+ 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||'non-standard plugin')+']';
+ }
+
+ plugins += '
'+ptext+'
';
+ } 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 += '
(unknown plugin: index '+i+')
';
+ }
+ }
+ plugins += '
';
+
+ var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
+ var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
+
+ var a = ''
+ + '
About IITC
'
+ + '
Ingress Intel Total Conversion
'
+ + ' '
+ + '
'
+ + ' IITC Homepage '
+ + ' On the script’s homepage you can:'
+ + '
'
+ + '
Find Updates
'
+ + '
Get Plugins
'
+ + '
Report Bugs
'
+ + '
Contribute!
'
+ + '
'
+ + '
'
+ + '
'
+ + ' MapQuest OSM tiles Courtesy of MapQuest'
+ + '
'
+ + ' '
+ + '
Version: ' + v + '
'
+ + '
Plugins: ' + plugins + '
'
+ + ' '
+ + '
' + attrib + '
'
+ + ' '
+ + '
' + contrib + '
';
+
+ 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) {
+ return (d+"").replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1 ");
+}
+
+
+window.requestParameterMunges = [
+ // now obsolete (they don't have some of the new parameters) munge sets deleted
+
+ // set 6 - 2013-10-29
+ {
+ 'dashboard.getGameScore': 'vzjhib746rvkre04', // GET_GAME_SCORE
+ 'dashboard.getPaginatedPlextsV2': 'gqa96zhqpddtfmkl', // GET_PAGINATED_PLEXTS
+ 'dashboard.getThinnedEntitiesV4': '18lmw7lytgxji0dk', // GET_THINNED_ENTITIES
+ 'dashboard.getPlayersByGuids': 'emb5xrj8rav1i0be', // LOOKUP_PLAYERS
+ 'dashboard.redeemReward': '4xqof5pldqab63rb', // REDEEM_REWARD
+ 'dashboard.sendInviteEmail': 'yq5wxjlnud0tj6hu', // SEND_INVITE_EMAIL
+ 'dashboard.sendPlext': 'e1ipqdxjlwd3l7zb', // SEND_PLEXT
+
+ // common parameters
+ method: 'wg7gyxoanqc1si5r',
+ version: 'adlo9o4kjvho5q94', //guessed parameter name - only seen munged
+ version_parameter: '56036a6497ea344a9fffa38b171a77c092c1f220', // passed as the value to the above parameter
+
+ // GET_THINNED_ENTITIES
+ quadKeys: '6vcl0ivqz4aj5sfu', //guessed parameter name - only seen munged
+
+ // GET_PAGINATED_PLEXTS
+ desiredNumItems: '6jd5b49wn748diye',
+ minLatE6: '891ebsryg45b8cxb',
+ minLngE6: 'mvepdcx1k6noya15',
+ maxLatE6: 's3rh3fhji5mcjlof',
+ maxLngE6: 'yqdgfuukrxj8byzj',
+ minTimestampMs: 'btf0kpztxrkt6sl6',
+ maxTimestampMs: 'hg8vhtehxf53n5cu',
+ chatTab: '6bk9rmebtk1ux6da', //guessed parameter name - only seen munged
+ ascendingTimestampOrder: '4zw3v6xwp117r47w',
+
+ // SEND_PLEXT
+ message: '55vpsci0hji0ai5x',
+ latE6: 'lyhrt4miuwc7w29d',
+ lngE6: 'c1yl2qmzfu5j23ao',
+// chatTab: '6bk9rmebtk1ux6da', //guessed parameter name - only seen munged
+
+ // LOOKUP_PLAYERS
+ guids: 'k76phw8ey9z21z7c',
+
+ // SEND_INVITE_EMAIL
+ inviteeEmailAddress: 'x16pe9u4i8bidbi2',
+ },
+
+ // set 7 - 2013-11-06
+ {
+ 'dashboard.getArtifactInfo': 'artifacts', // GET_ARTIFACT_INFO: new (and not obfsucated?!)
+ 'dashboard.getGameScore': 'yol4dxx5ufqolhk2', // GET_GAME_SCORE
+ 'dashboard.getPaginatedPlextsV2': '7b83j2z81rtk6101', // GET_PAGINATED_PLEXTS
+ 'dashboard.getThinnedEntitiesV4': '46su4lrisoq28gxh', // GET_THINNED_ENTITIES
+ 'dashboard.getPlayersByGuids': 'wsc5puahrymtf1qh', // LOOKUP_PLAYERS
+ 'dashboard.redeemReward': 'oo0n7pw2m0xufpzx', // REDEEM_REWARD
+ 'dashboard.sendInviteEmail': 'bo1bp74rz8kbdjkb', // SEND_INVITE_EMAIL
+ 'dashboard.sendPlext': 'q0f8o4v9t8pt91yv', // SEND_PLEXT
+
+ // common parameters
+ method: 'imo60cdzkemxduub',
+ version: '54lh4o0q7nz7dao9', //guessed parameter name - only seen munged
+ version_parameter: '370c0b4e160ed26c8c4ce40f10f546545730e1ef', // passed as the value to the above parameter
+
+ // GET_THINNED_ENTITIES
+ quadKeys: 'iqy8e2d3zpne0cmh', //guessed parameter name - only seen munged
+
+ // GET_PAGINATED_PLEXTS
+ desiredNumItems: 'chwe3yko3xy0qlk3',
+ minLatE6: 'f31z3x27ua8i05cf',
+ minLngE6: 't0rmob7f42c0w04r',
+ maxLatE6: 'ebwfvri5io9q0tvu',
+ maxLngE6: 'lfqzvpj92dp8uxo6',
+ minTimestampMs: '23a6djyyieeaeduu',
+ maxTimestampMs: 'zhjtsm2gw7w3b7mx',
+ chatTab: 'tak64gipm3hhqpnh', //guessed parameter name - only seen munged
+ ascendingTimestampOrder: 'v5rzzxtg5rmry3dx',
+
+ // SEND_PLEXT
+ message: 'onptntn3szan21lj',
+ latE6: '1jq9lgu3hjajrt7s',
+ lngE6: 'plbubiopnavbxxh6',
+// chatTab: 'tak64gipm3hhqpnh', //guessed parameter name - only seen munged
+
+ // LOOKUP_PLAYERS
+ guids: '919p2cfpdo2wz03n',
+
+ // SEND_INVITE_EMAIL
+ inviteeEmailAddress: 'thpbnoyjx0antwm5',
+ },
+
+];
+window.activeRequestMungeSet = undefined;
+
+// attempt to guess the munge set in use, by looking therough the functions of the stock intel page for one of the munged params
+window.detectActiveMungeSet = function() {
+
+ // try and find the stock page functions
+ // FIXME? revert to searching through all the code? is that practical?
+ var stockFunc;
+ try {
+ stockFunc = nemesis.dashboard.network.XhrController.prototype.sendRequest.toString();
+ } catch(e) {
+ try {
+ stockFunc = nemesis.dashboard.network.DataFetcher.prototype.sendRequest_.toString();
+ } catch(e) {
+ }
+ }
+
+ if(stockFunc) {
+ for (var i in window.requestParameterMunges) {
+ if (stockFunc.indexOf (window.requestParameterMunges[i]['method']) >= 0) {
+ console.log('IITC: found request munge set index '+i+' in stock intel site');
+ window.activeRequestMungeSet = i;
+ }
+ }
+ } else {
+ console.error('IITC: failed to find the stock site function for detecting munge set');
+ }
+
+ if (window.activeRequestMungeSet===undefined) {
+ console.error('IITC: failed to find request munge set - IITC will likely fail');
+ window.activeRequestMungeSet = 0;
+ }
+}
+
+// niantic now add some munging to the request parameters. so far, only two sets of this munging have been seen
+window.requestDataMunge = function(data) {
+ var activeMunge = window.requestParameterMunges[window.activeRequestMungeSet];
+
+ function munge(obj) {
+ if (Object.prototype.toString.call(obj) === '[object Array]') {
+ // an array - munge each element of it
+ var newobj = [];
+ for (var i in obj) {
+ newobj[i] = munge(obj[i]);
+ }
+ return newobj;
+ } else if (typeof obj === 'object') {
+ // an object: munge each property name, and pass the value through the munge process
+ var newobj = Object();
+ for (var p in obj) {
+ var m = activeMunge[p];
+ if (m === undefined) {
+ console.error('Error: failed to find munge for object property '+p);
+ newobj[p] = obj[p];
+ } else {
+ // rename the property
+ newobj[m] = munge(obj[p]);
+ }
+ }
+ return newobj;
+ } else {
+ // neither an array or an object - so must be a simple value. return it unmodified
+ return obj;
+ }
+ };
+
+ var newdata = munge(data);
+ return newdata;
+}
+
+// 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) {
+ if (window.activeRequestMungeSet===undefined) {
+ window.detectActiveMungeSet();
+ }
+ var activeMunge = window.requestParameterMunges[window.activeRequestMungeSet];
+
+ var methodName = 'dashboard.'+action;
+ var versionStr = 'version_parameter';
+
+ // munging of the method name - seen in Set 2 (onwards?)
+ methodName = activeMunge[methodName];
+ // and of the 'version' parameter
+ versionStr = activeMunge[versionStr];
+
+ var post_data = JSON.stringify(window.requestDataMunge($.extend({method: methodName, version: versionStr}, data)));
+ 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 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)
+ +' '+d.toLocaleTimeString()+(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) {
+
+ var h = Math.floor(seconds / 3600);
+ var m = Math.floor((seconds % 3600) / 60);
+ var s = seconds % 60;
+
+ var text = '';
+ if (h > 0) text += h+'h';
+ if (m > 0) text += m+'m';
+ if (s > 0 || text == '') text += s+'s';
+
+ return text;
+}
+
+
+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 = '';
+ var script = '';
+ var gmaps = 'Google Maps';
+ var bingmaps = 'Bing Maps';
+ var osm = 'OpenStreetMap';
+ var latLng = '<' + lat + ',' + lng +'>';
+ dialog({
+ html: '
',
+ title: name,
+ id: 'poslinks'
+ });
+ }
+}
+
+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.copy)
+ return true; // i.e. execute other actions
+
+ var center = map.getCenter();
+ android.intentPosLink(center.lat, center.lng, map.getZoom(), "Intel Map", false);
+ return false;
+}
+
+
+window.getPortalDataZoom = function() {
+ var mapZoom = map.getZoom();
+
+ // make sure we're dealing with an integer here
+ // (mobile: a float somehow gets through in some cases!)
+ var z = parseInt(mapZoom);
+
+ // limiting the mazimum zoom level for data retrieval reduces the number of requests at high zoom levels
+ // (as all portal data is retrieved at z=17, why retrieve multiple z=18 tiles when fewer z=17 would do?)
+ // very effective along with the new cache code
+ if (z > 17) z=17;
+
+ //sanity check - should never happen
+ if (z < 0) z=0;
+
+ return z;
+}
+
+
+window.getMinPortalLevelForZoom = function(z) {
+//based on code from stock gen_dashboard.js
+ var ZOOM_TO_LEVEL = [8, 8, 8, 8, 7, 7, 6, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1];
+ var l = ZOOM_TO_LEVEL[z] || 0;
+ return l;
+}
+
+
+window.getMinPortalLevel = function() {
+ var z = getPortalDataZoom();
+ return getMinPortalLevelForZoom(z);
+}
+
+// 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 '
'+detail[1]+'
'+detail[0]+'
';
+ else
+ return '
'+detail[0]+'
'+detail[1]+'
';
+ }).join('');
+ if(t.length % 2 === 1) t + '
';
+ 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, ' ');
+
+ 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 = '
';
+ }
for(var i = evtsLength - 2; i >= 0 && i >= evtsLength - 10; i--) {
var ev = playerData.events[i];
- title += ago(ev.time, now) + minsAgo + window.chat.getChatPortalName(ev) + '\n';
+ title += '
' + ago(ev.time, now) + '
'
+ + '
ago
'
+ + '
' + window.chat.getChatPortalName(ev) + '
';
}
+ if(evtsLength >= 2)
+ title += '
';
// calculate the closest portal to the player
var eventPortal = []
@@ -329,13 +343,13 @@ window.plugin.playerTracker.drawData = function() {
// marker itself
var icon = playerData.team === 'RESISTANCE' ? new plugin.playerTracker.iconRes() : new plugin.playerTracker.iconEnl();
- var m = L.marker(gllfe(last), {title: title, icon: icon, referenceToPortal: closestPortal, opacity: absOpacity});
- // ensure tooltips are closed, sometimes they linger
- m.on('mouseout', function() { $(this._icon).tooltip('close'); });
+// as per OverlappingMarkerSpiderfier docs, click events (popups, etc) must be handled via it rather than the standard
+// marker click events. so store the popup text in the options, then display it in the oms click handler
+ var m = L.marker(gllfe(last), {icon: icon, referenceToPortal: closestPortal, opacity: absOpacity, desc: title});
+// m.bindPopup(title);
+
m.addTo(playerData.team === 'RESISTANCE' ? plugin.playerTracker.drawnTracesRes : plugin.playerTracker.drawnTracesEnl);
plugin.playerTracker.oms.addMarker(m);
- // jQueryUI doesn’t automatically notice the new markers
- window.setupTooltips($(m._icon));
});
// draw the poly lines to the map
@@ -372,13 +386,6 @@ window.plugin.playerTracker.handleData = function(data) {
plugin.playerTracker.discardOldData();
plugin.playerTracker.processNewData(data);
- // remove old popups
- plugin.playerTracker.drawnTracesEnl.eachLayer(function(layer) {
- if(layer._icon) $(layer._icon).tooltip('destroy');
- });
- plugin.playerTracker.drawnTracesRes.eachLayer(function(layer) {
- if(layer._icon) $(layer._icon).tooltip('destroy');
- });
plugin.playerTracker.oms.clearMarkers();
plugin.playerTracker.drawnTracesEnl.clearLayers();
plugin.playerTracker.drawnTracesRes.clearLayers();
diff --git a/plugins/portals-list.user.js b/plugins/portals-list.user.js
index 2f34d169..5f03b471 100644
--- a/plugins/portals-list.user.js
+++ b/plugins/portals-list.user.js
@@ -2,7 +2,7 @@
// @id iitc-plugin-portals-list@teo96
// @name IITC plugin: show list of portals
// @category Info
-// @version 0.0.17.@@DATETIMEVERSION@@
+// @version 0.0.18.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@@ -211,11 +211,14 @@ window.plugin.portalslist.displayPL = function() {
$(document).on('click.portalslist', '#portalslist .filterEnl', function() {
$('#portalslist').html(window.plugin.portalslist.portalTable($(this).data('sort'),window.plugin.portalslist.sortOrder,2));
});
+
+ //run the name resolving process
+ resolvePlayerNames();
//debug tools
//end = new Date().getTime();
//console.log('***** end : ' + end + ' and Elapse : ' + (end - start));
- }
+}
window.plugin.portalslist.portalTable = function(sortBy, sortOrder, filter) {
// sortOrder <0 ==> desc, >0 ==> asc, i use sortOrder * -1 to change the state
diff --git a/plugins/scoreboard.user.js b/plugins/scoreboard.user.js
index 2a0d4b42..22bd189b 100644
--- a/plugins/scoreboard.user.js
+++ b/plugins/scoreboard.user.js
@@ -2,7 +2,7 @@
// @id iitc-plugin-scoreboard@vita10gy
// @name IITC plugin: show a localized scoreboard.
// @category Info
-// @version 0.1.8.@@DATETIMEVERSION@@
+// @version 0.1.9.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@@ -291,6 +291,9 @@ window.plugin.scoreboard.display = function() {
$(document).on('click', '#players table th', function() {
$('#players').html(window.plugin.scoreboard.playerTable($(this).data('sort')));
});
+
+ //run the name resolving process
+ resolvePlayerNames();
}
window.plugin.scoreboard.portalDistance = function(portalAE6Location, portalBE6Location) {
diff --git a/style.css b/style.css
index a368573e..a04b3086 100644
--- a/style.css
+++ b/style.css
@@ -982,3 +982,53 @@ td + td {
display: none;
}
+
+
+/* leaflet popups - restyle to match the theme of IITC */
+#map .leaflet-popup {
+ pointer-events: none;
+}
+
+#map .leaflet-popup-content-wrapper {
+ border-radius: 0px;
+ -webkit-border-radius: 0px;
+ border: 1px solid #20A8B1;
+ background: #0e3d4e;
+ pointer-events: auto;
+}
+
+#map .leaflet-popup-content {
+ color: #ffce00;
+ margin: 5px 8px;
+}
+
+#map .leaflet-popup-close-button {
+ padding: 2px 1px 0 0;
+ font-size: 12px;
+ line-height: 8px;
+ width: 10px;
+ height: 10px;
+ pointer-events: auto;
+}
+
+
+#map .leaflet-popup-tip {
+ /* change the tip from an arrow to a simple line */
+ background: #20A8B1;
+ width: 1px;
+ height: 20px;
+ padding: 0;
+ margin: 0 0 0 20px;
+ -webkit-transform: none;
+ -moz-transform: none;
+ -ms-transform: none;
+ -o-transform: none;
+ transform: none;
+}
+
+
+/* misc */
+
+.no-pointer-events {
+ pointer-events: none;
+}
diff --git a/website/page/home.php b/website/page/home.php
index 1eda08fc..02ae9641 100644
--- a/website/page/home.php
+++ b/website/page/home.php
@@ -13,6 +13,12 @@ offers many more features. It is available for
Latest news
+
6th November 2013
+
+IITC 0.14.5 and IITC Mobile 0.7.7.1 have been released. This contains a fix to work with the latest intel site updates.
+Other than this, it is identical to the 0.14.4/0.7.7 release.
+
+
29th October 2013
IITC 0.14.4 and IITC Mobile 0.7.7 have just been released. A critical update required to work with changes made to the
diff --git a/website/page/news.php b/website/page/news.php
index e2e63673..0edb63e3 100644
--- a/website/page/news.php
+++ b/website/page/news.php
@@ -1,5 +1,11 @@
News
+
6th November 2013
+
+IITC 0.14.5 and IITC Mobile 0.7.7.1 have been released. This contains a fix to work with the latest intel site updates.
+Other than this, it is identical to the 0.14.4/0.7.7 release.
+
+
29th October 2013
IITC 0.14.4 and IITC Mobile 0.7.7 have just been released. A critical update required to work with changes made to the