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 = ''; + + 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:' + + ' ' + + '
' + + '
' + + ' 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: '
' + qrcode + script + gmaps + '; ' + bingmaps + '; ' + osm + '
' + latLng + '
', + 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 = ''; + $.each(data, function(i, row) { + table += ''; + $.each(data[i], function(k, cell) { + var attributes = ''; + if(k === 0 && data[i].length < columnCount) { + attributes = ' colspan="'+(columnCount - data[i].length + 1)+'"'; + } + table += ''+cell+''; + }); + 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(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); + } +} + diff --git a/external/oms.min.js b/external/oms.min.js index a53aede5..3b426845 100644 --- a/external/oms.min.js +++ b/external/oms.min.js @@ -5,15 +5,15 @@ Copyright (c) 2011 - 2012 George MacKerron Released under the MIT licence: http://opensource.org/licenses/mit-license Note: The Leaflet maps API must be included *before* this code */ -(function(){var n={}.hasOwnProperty,o=[].slice;null!=this.L&&(this.OverlappingMarkerSpiderfier=function(){function l(c,b){var a,e,g,f,d=this;this.map=c;null==b&&(b={});for(a in b)n.call(b,a)&&(e=b[a],this[a]=e);this.initMarkerArrays();this.listeners={};f=["click","zoomend"];e=0;for(g=f.length;eb)return this;a=this.markerListeners.splice(b,1)[0];c.removeEventListener("click",a);delete c._oms;this.markers.splice(b,1);return this};d.clearMarkers=function(){var c,b,a,e,g;this.unspiderfy();g=this.markers;c=a=0;for(e=g.length;aa||this.listeners[c].splice(a,1);return this};d.clearListeners=function(c){this.listeners[c]=[];return this};d.trigger=function(){var c,b,a,e,g,f;b=arguments[0];c=2<=arguments.length?o.call(arguments,1):[];b=null!=(a=this.listeners[b])?a:[];f=[];e=0;for(g=b.length;ec;a=0<=c?++f:--f)a=this.circleStartAngle+a*e,d.push(new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)));return d};d.generatePtsSpiral=function(c,b){var a,e,g,f,d;g=this.spiralLengthStart;a=0;d=[];for(e=f=0;0<=c?fc;e=0<=c?++f:--f)a+=this.spiralFootSeparation/g+5.0E-4*e,e=new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)),g+=i*this.spiralLengthFactor/a,d.push(e);return d};d.spiderListener=function(c){var b,a,e,g,f,d,h,i,j;b= -null!=c._omsData;(!b||!this.keepSpiderfied)&&this.unspiderfy();if(b)return this.trigger("click",c);g=[];f=[];d=this.nearbyDistance*this.nearbyDistance;e=this.map.latLngToLayerPoint(c.getLatLng());j=this.markers;h=0;for(i=j.length;h=this.circleSpiralSwitchover?this.generatePtsSpiral(k,a).reverse():this.generatePtsCircle(k,a);a=function(){var a,b,i,k=this;i=[];a=0;for(b=d.length;aa||this.listeners[c].splice(a,1);return this};d.clearListeners=function(c){this.listeners[c]=[];return this};d.trigger=function(){var c,b,a,e,g,f;b=arguments[0];c=2<=arguments.length?r.call(arguments,1):[];b=null!=(a=this.listeners[b])?a:[];f=[];e=0;for(g=b.length;ec;a=0<=c?++f:--f)a=this.circleStartAngle+a*e,d.push(new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)));return d};d.generatePtsSpiral=function(c,b){var a,e,g,f,d;g=this.spiralLengthStart;a=0;d=[];for(e=f=0;0<=c?fc;e=0<=c?++f:--f)a+=this.spiralFootSeparation/g+5E-4*e,e=new L.Point(b.x+g*Math.cos(a),b.y+g*Math.sin(a)),g+=k*this.spiralLengthFactor/a,d.push(e);return d};d.spiderListener=function(c){var b,a,e,g,f,d,h,k,l;(b=null!= +c._omsData)&&this.keepSpiderfied||this.unspiderfy();if(b)return this.trigger("click",c);g=[];f=[];d=this.nearbyDistance*this.nearbyDistance;e=this.map.latLngToLayerPoint(c.getLatLng());l=this.markers;h=0;for(k=l.length;h=this.circleSpiralSwitchover?this.generatePtsSpiral(m,a).reverse():this.generatePtsCircle(m,a);a=function(){var a,b,k,m=this;k=[];a=0;for(b=d.length;a - + - - - - - Android 4.3 Platform - - - - - - - diff --git a/mobile/AndroidManifest.xml b/mobile/AndroidManifest.xml index 10f69ab0..21fa7ea6 100644 --- a/mobile/AndroidManifest.xml +++ b/mobile/AndroidManifest.xml @@ -2,12 +2,12 @@ + android:versionCode="59" + android:versionName="0.9"> + android:targetSdkVersion="19"/> @@ -23,7 +23,7 @@ android:label="@string/app_name" android:theme="@style/AppTheme"> + android:value=".IITC_Mobile"/> + android:value=".IITC_Mobile"/> diff --git a/mobile/plugins/user-location.user.js b/mobile/plugins/user-location.user.js index 4dbd30ba..aefe7b62 100644 --- a/mobile/plugins/user-location.user.js +++ b/mobile/plugins/user-location.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @id iitc-plugin-user-location@cradle // @name IITC plugin: User Location -// @version 0.1.3.@@DATETIMEVERSION@@ +// @version 0.1.4.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -35,10 +35,11 @@ window.plugin.userLocation.setup = function() { var title = '' + PLAYER.nickname + '\'s location'; var marker = L.marker(window.map.getCenter(), { - title: title, icon: new plugin.userLocation.icon() }); + marker.bindPopup(title); + plugin.userLocation.marker = marker; marker.addTo(window.map); // jQueryUI doesn’t automatically notice the new markers diff --git a/mobile/project.properties b/mobile/project.properties index ce39f2d0..4ab12569 100644 --- a/mobile/project.properties +++ b/mobile/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-18 +target=android-19 diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java b/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java index 78736a9e..fe1680cb 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java @@ -104,6 +104,11 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { } } + @Override + protected boolean isValidFragment(String s) { + return true; + } + // called by Plugins Fragment public static ArrayList getPluginPreference(String key) { return sPlugins.get(key); diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java index 5754c1ec..2519eae8 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java @@ -16,6 +16,7 @@ import android.view.View; import android.view.WindowManager; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; @@ -138,24 +139,9 @@ public class IITC_WebView extends WebView { @Override public void loadUrl(String url) { - // if in edit text mode, don't load javascript otherwise the keyboard closes. - HitTestResult testResult = getHitTestResult(); - if (url.startsWith("javascript:") && testResult != null && - testResult.getType() == HitTestResult.EDIT_TEXT_TYPE) { - // let window.show(...) interupt input - // window.show(...) is called if one of the action bar buttons - // is clicked - if (!url.startsWith("javascript: window.show(")) { - Log.d("iitcm", "in insert mode. do not load script."); - return; - } - } - // do nothing if script is enabled; - if (mDisableJs) { - Log.d("iitcm", "javascript injection disabled...return"); - return; - } - if (!url.startsWith("javascript:")) { + if (url.startsWith("javascript:")) { + loadJS(url.substring("javascript:".length())); + } else { // force https if enabled in settings SharedPreferences sharedPref = PreferenceManager .getDefaultSharedPreferences(getContext()); @@ -168,8 +154,38 @@ public class IITC_WebView extends WebView { // disable splash screen if a http error code is responded new CheckHttpResponse(mJsInterface, mIitc).execute(url); Log.d("iitcm", "loading url: " + url); + super.loadUrl(url); + } + } + + public void loadJS(String js) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + evaluateJavascript(js, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + // maybe we want to add stuff here + return; + } + }); + } else { + // if in edit text mode, don't load javascript otherwise the keyboard closes. + HitTestResult testResult = getHitTestResult(); + if (testResult != null && testResult.getType() == HitTestResult.EDIT_TEXT_TYPE) { + // let window.show(...) interupt input + // window.show(...) is called if one of the action bar buttons + // is clicked + if (!js.startsWith("window.show(")) { + Log.d("iitcm", "in insert mode. do not load script."); + return; + } + } + // do nothing if script is enabled; + if (mDisableJs) { + Log.d("iitcm", "javascript injection disabled...return"); + return; + } + super.loadUrl("javascript:" + js); } - super.loadUrl(url); } @Override diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java index 1749b3a9..48f711cc 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java @@ -43,6 +43,7 @@ public class IITC_WebViewClient extends WebViewClient { private String mIitcScript = null; private String mIitcPath = null; + private boolean mIitcInjected = false; private final Context mContext; public IITC_WebViewClient(Context c) { @@ -142,6 +143,7 @@ public class IITC_WebViewClient extends WebViewClient { } else { js = this.fileToString("total-conversion-build.user.js", true); } + mIitcInjected = false; } PackageManager pm = mContext.getPackageManager(); @@ -177,8 +179,10 @@ public class IITC_WebViewClient extends WebViewClient { public void onPageFinished(WebView view, String url) { if (url.startsWith("http://www.ingress.com/intel") || url.startsWith("https://www.ingress.com/intel")) { + if (mIitcInjected) return; Log.d("iitcm", "injecting iitc.."); view.loadUrl("javascript: " + this.mIitcScript); + mIitcInjected = true; loadPlugins(view); } super.onPageFinished(view, url); diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java b/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java index 621f023c..235701bf 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java @@ -152,11 +152,19 @@ public class IntentListView extends ListView { ResolveInfo info = activityList.get(i); ActivityInfo activity = info.activityInfo; + // fix bug in PackageManager - a replaced package name might cause non-exported intents to appear + if (activity.exported == false && !activity.packageName.equals(packageName)) { + activityList.remove(i); + i--; + continue; + } + // remove all IITCm intents, except for SendToClipboard in case Drive is not installed if (activity.packageName.equals(packageName)) { if (hasCopyIntent || !activity.name.equals(SendToClipboard.class.getCanonicalName())) { activityList.remove(i); i--; + continue; } } } diff --git a/plugins/add-kml.user.js b/plugins/add-kml.user.js index 45c4ce2c..0ff7d41b 100755 --- a/plugins/add-kml.user.js +++ b/plugins/add-kml.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @id overlay-kml@danielatkins // @name IITC plugin: overlay KML -// @category Info -// @version 0.1.@@DATETIMEVERSION@@ +// @category Layer +// @version 0.2.0.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ diff --git a/plugins/basemap-bing.user.js b/plugins/basemap-bing.user.js new file mode 100644 index 00000000..9a1f4adc --- /dev/null +++ b/plugins/basemap-bing.user.js @@ -0,0 +1,174 @@ +// ==UserScript== +// ==UserScript== +// @id iitc-plugin-bing-maps +// @name IITC plugin: Bing maps +// @category Map Tiles +// @version 0.1.0.@@DATETIMEVERSION@@ +// @namespace https://github.com/jonatkins/ingress-intel-total-conversion +// @updateURL @@UPDATEURL@@ +// @downloadURL @@DOWNLOADURL@@ +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Add the maps.bing.com map layers ( +// @include https://www.ingress.com/intel* +// @include http://www.ingress.com/intel* +// @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @grant none +// ==/UserScript== + +@@PLUGINSTART@@ + +// PLUGIN START //////////////////////////////////////////////////////// + +window.plugin.mapBing = function() {}; + +window.plugin.mapBing.setupBingLeaflet = function() { +//--------------------------------------------------------------------- +// https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Bing.js +L.BingLayer = L.TileLayer.extend({ + options: { + subdomains: [0, 1, 2, 3], + type: 'Aerial', + attribution: 'Bing', + culture: '' + }, + + initialize: function(key, options) { + L.Util.setOptions(this, options); + + this._key = key; + this._url = null; + this.meta = {}; + this.loadMetadata(); + }, + + tile2quad: function(x, y, z) { + var quad = ''; + for (var i = z; i > 0; i--) { + var digit = 0; + var mask = 1 << (i - 1); + if ((x & mask) != 0) digit += 1; + if ((y & mask) != 0) digit += 2; + quad = quad + digit; + } + return quad; + }, + + getTileUrl: function(p, z) { + var z = this._getZoomForUrl(); + var subdomains = this.options.subdomains, + s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)]; + return this._url.replace('{subdomain}', s) + .replace('{quadkey}', this.tile2quad(p.x, p.y, z)) + .replace('{culture}', this.options.culture); + }, + + loadMetadata: function() { + // TODO? modify this to cache the metadata in - say - sessionStorage? localStorage? + var _this = this; + var cbid = '_bing_metadata_' + L.Util.stamp(this); + window[cbid] = function (meta) { + _this.meta = meta; + window[cbid] = undefined; + var e = document.getElementById(cbid); + e.parentNode.removeChild(e); + if (meta.errorDetails) { + alert("Got metadata" + meta.errorDetails); + return; + } + _this.initMetadata(); + }; + var url = "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.options.type + "?include=ImageryProviders&jsonp=" + cbid + "&key=" + this._key; + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = cbid; + document.getElementsByTagName("head")[0].appendChild(script); + }, + + initMetadata: function() { + var r = this.meta.resourceSets[0].resources[0]; + this.options.subdomains = r.imageUrlSubdomains; + this._url = r.imageUrl; + this._providers = []; + for (var i = 0; i < r.imageryProviders.length; i++) { + var p = r.imageryProviders[i]; + for (var j = 0; j < p.coverageAreas.length; j++) { + var c = p.coverageAreas[j]; + var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false}; + var bounds = new L.LatLngBounds( + new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01), + new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01) + ); + coverage.bounds = bounds; + coverage.attrib = p.attribution; + this._providers.push(coverage); + } + } + this._update(); + }, + + _update: function() { + if (this._url == null || !this._map) return; + this._update_attribution(); + L.TileLayer.prototype._update.apply(this, []); + }, + + _update_attribution: function() { + var bounds = this._map.getBounds(); + var zoom = this._map.getZoom(); + for (var i = 0; i < this._providers.length; i++) { + var p = this._providers[i]; + if ((zoom <= p.zoomMax && zoom >= p.zoomMin) && + bounds.intersects(p.bounds)) { + if (!p.active) + this._map.attributionControl.addAttribution(p.attrib); + p.active = true; + } else { + if (p.active) + this._map.attributionControl.removeAttribution(p.attrib); + p.active = false; + } + } + }, + + onRemove: function(map) { + for (var i = 0; i < this._providers.length; i++) { + var p = this._providers[i]; + if (p.active) { + this._map.attributionControl.removeAttribution(p.attrib); + p.active = false; + } + } + L.TileLayer.prototype.onRemove.apply(this, [map]); + } +}); +//--------------------------------------------------------------------- +} + + +window.plugin.mapBing.setup = function() { + window.plugin.mapBing.setupBingLeaflet(); + + //set this to your API key + var bingApiKey = 'ArR2hTa2C9cRQZT-RmgrDkfvh3PwEVRl0gB34OO4wJI7vQNElg3DDWvbo5lfUs3p'; + + var bingTypes = { + 'Road': "Road", + 'Aerial': "Aerial", + 'AerialWithLabels': "Aerial with labels", + }; + + for (type in bingTypes) { + var name = bingTypes[type]; + var bingMap = new L.BingLayer(bingApiKey, {type: type, maxZoom:20}); + layerChooser.addBaseLayer(bingMap, 'Bing '+name); + } + +}; + +var setup = window.plugin.mapBing.setup; + +// PLUGIN END ////////////////////////////////////////////////////////// + + +@@PLUGINEND@@ diff --git a/plugins/basemap-nokia-ovi.user.js b/plugins/basemap-nokia-ovi.user.js new file mode 100644 index 00000000..e345d921 --- /dev/null +++ b/plugins/basemap-nokia-ovi.user.js @@ -0,0 +1,48 @@ +// ==UserScript== +// @id iitc-plugin-nokia-ovi-maps +// @name IITC plugin: Nokia OVI maps +// @category Map Tiles +// @version 0.1.0.@@DATETIMEVERSION@@ +// @namespace https://github.com/jonatkins/ingress-intel-total-conversion +// @updateURL @@UPDATEURL@@ +// @downloadURL @@DOWNLOADURL@@ +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Add various map layers from Nokia OVI Maps +// @include https://www.ingress.com/intel* +// @include http://www.ingress.com/intel* +// @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @grant none +// ==/UserScript== + +@@PLUGINSTART@@ + +// PLUGIN START //////////////////////////////////////////////////////// + +window.plugin.mapNokiaOvi = function() {}; + +window.plugin.mapNokiaOvi.setup = function() { + //the list of styles you'd like to see + var oviStyles = { + 'normal.day': "Normal", + 'normal.day.grey': "Normal (grey)", + 'normal.day.transit': "Normal (transit)", + 'satellite.day': "Satellite", + 'terrain.day': "Terrain", + }; + + + var oviOpt = {attribution: 'Imagery © Nokia OVI', maxZoom: 20}; + + $.each(oviStyles, function(key,value) { + oviOpt['style'] = key; + var oviMap = new L.TileLayer('http://maptile.maps.svc.ovi.com/maptiler/maptile/newest/{style}/{z}/{x}/{y}/256/png8', oviOpt); + layerChooser.addBaseLayer(oviMap, 'Nokia OVI '+value); + }); + +}; + +var setup = window.plugin.mapNokiaOvi.setup; + +// PLUGIN END ////////////////////////////////////////////////////////// + +@@PLUGINEND@@ diff --git a/plugins/basemap-stamen.user.js b/plugins/basemap-stamen.user.js new file mode 100644 index 00000000..2478e076 --- /dev/null +++ b/plugins/basemap-stamen.user.js @@ -0,0 +1,56 @@ +// ==UserScript== +// @id iitc-plugin-basemap-stamen@jonatkins +// @name IITC plugin: Map layers from stamen.com +// @category Map Tiles +// @version 0.1.0.@@DATETIMEVERSION@@ +// @namespace https://github.com/jonatkins/ingress-intel-total-conversion +// @updateURL @@UPDATEURL@@ +// @downloadURL @@DOWNLOADURL@@ +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Adds the 'Toner' and 'Watercolor' map layers from maps.stamen.com +// @include https://www.ingress.com/intel* +// @include http://www.ingress.com/intel* +// @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @grant none +// ==/UserScript== + +@@PLUGINSTART@@ + +// PLUGIN START //////////////////////////////////////////////////////// + + +// use own namespace for plugin +window.plugin.mapTileStamen = function() {}; + +window.plugin.mapTileStamen.setup = function() { + + load('http://maps.stamen.com/js/tile.stamen.js?v1.2.3').thenRun(window.plugin.mapTileStamen.addLayer); +} + +window.plugin.mapTileStamen.addLayer = function() { + + var types = { + 'toner': 'Toner', +// 'toner-hybrid': 'Toner Hybrid', // transparent layer. could be usefun over satelliate imagery or similar +// 'toner-labels': 'Toner Labels', // transparent layer. could be usefun over satelliate imagery or similar +// 'toner-lines': 'Toner Lines', // transparent layer. could be usefun over satelliate imagery or similar + 'toner-background': 'Toner Background', + 'toner-lite': 'Toner Lite', + 'watercolor': 'Watercolor', + }; + + for (var type in types) { + var name = types[type]; + + var layer = new L.StamenTileLayer(type); + + layerChooser.addBaseLayer(layer,'Stamen '+name); + } + +}; + +var setup = window.plugin.mapTileStamen.setup; + +// PLUGIN END ////////////////////////////////////////////////////////// + +@@PLUGINEND@@ diff --git a/plugins/player-tracker.user.js b/plugins/player-tracker.user.js index f79e382a..eca4bf66 100644 --- a/plugins/player-tracker.user.js +++ b/plugins/player-tracker.user.js @@ -2,7 +2,7 @@ // @id iitc-plugin-player-tracker@breunigs // @name IITC Plugin: Player tracker // @category Layer -// @version 0.9.6.@@DATETIMEVERSION@@ +// @version 0.10.0.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -62,12 +62,20 @@ window.plugin.playerTracker.setup = function() { }); } }); - plugin.playerTracker.oms = new OverlappingMarkerSpiderfier(map); + plugin.playerTracker.oms = new OverlappingMarkerSpiderfier(map, {keepSpiderfied: true, legWeight: 3.5}); plugin.playerTracker.oms.legColors = {'usual': '#FFFF00', 'highlighted': '#FF0000'}; - plugin.playerTracker.oms.legWeight = 3.5; + + var playerPopup = new L.Popup({offset: L.point([0,-20])}); plugin.playerTracker.oms.addListener('click', function(player) { window.renderPortalDetails(player.options.referenceToPortal); + playerPopup.setContent(player.options.desc); + playerPopup.setLatLng(player.getLatLng()); + map.openPopup(playerPopup) }); + plugin.playerTracker.oms.addListener('spiderfy', function(markers) { + map.closePopup(); + }); + addHook('publicChatDataAvailable', window.plugin.playerTracker.handleData); @@ -226,6 +234,7 @@ window.plugin.playerTracker.processNewData = function(data) { } window.plugin.playerTracker.getLatLngFromEvent = function(ev) { +//TODO? add weight to certain events, or otherwise prefer them, to give better locations? var lats = 0; var lngs = 0; $.each(ev.latlngs, function() { @@ -275,13 +284,13 @@ window.plugin.playerTracker.drawData = function() { polyLineByAgeEnl[ageBucket].push(line); } - // tooltip for marker + // popup for marker var evtsLength = playerData.events.length; var last = playerData.events[evtsLength-1]; var ago = plugin.playerTracker.ago; var cssClass = playerData.team === 'RESISTANCE' ? 'res' : 'enl'; var title = '' + playerData.nick + ''; - + if(window.plugin.guessPlayerLevels !== undefined && window.plugin.guessPlayerLevels.fetchLevelByPlayer !== undefined) { var playerLevel = window.plugin.guessPlayerLevels.fetchLevelByPlayer(pguid); @@ -295,17 +304,22 @@ window.plugin.playerTracker.drawData = function() { } } - title += '\n' - + ago(last.time, now) + ' ago\n' + title += '
' + + ago(last.time, now) + ' ago
' + window.chat.getChatPortalName(last); // show previous data in tooltip - var minsAgo = '\t ago\t'; - if(evtsLength >= 2) - title += '\n \nprevious locations:\n'; + if(evtsLength >= 2) { + title += '
 
previous locations:
' + + ''; + } 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 += '' + + '' + + ''; } + if(evtsLength >= 2) + title += '
' + ago(ev.time, now) + 'ago' + window.chat.getChatPortalName(ev) + '
'; // 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