// ==UserScript== // @id iitc-plugin-portals-list@teo96 // @name IITC plugin: show list of portals // @category Info // @version 0.2.1.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ // @description [@@BUILDNAME@@-@@BUILDDATE@@] Display a sortable list of all visible portals with full details about the team, resonators, links, etc. // @include https://*.ingress.com/intel* // @include http://*.ingress.com/intel* // @match https://*.ingress.com/intel* // @match http://*.ingress.com/intel* // @include https://*.ingress.com/mission/* // @include http://*.ingress.com/mission/* // @match https://*.ingress.com/mission/* // @match http://*.ingress.com/mission/* // @grant none // ==/UserScript== @@PLUGINSTART@@ // PLUGIN START //////////////////////////////////////////////////////// // use own namespace for plugin window.plugin.portalslist = function() {}; window.plugin.portalslist.listPortals = []; window.plugin.portalslist.sortBy = 1; // second column: level window.plugin.portalslist.sortOrder = -1; window.plugin.portalslist.enlP = 0; window.plugin.portalslist.resP = 0; window.plugin.portalslist.neuP = 0; window.plugin.portalslist.filter = 0; /* * plugins may add fields by appending their specifiation to the following list. The following members are supported: * title: String * Name of the column. Required. * value: function(portal) * The raw value of this field. Can by anything. Required, but can be dummy implementation if sortValue and format * are implemented. * sortValue: function(value, portal) * The value to sort by. Optional, uses value if omitted. The raw value is passed as first argument. * sort: function(valueA, valueB, portalA, portalB) * Custom sorting function. See Array.sort() for details on return value. Both the raw values and the portal objects * are passed as arguments. Optional. Set to null to disable sorting * format: function(cell, portal, value) * Used to fill and format the cell, which is given as a DOM node. If omitted, the raw value is put in the cell. * defaultOrder: -1|1 * Which order should by default be used for this column. -1 means descending. Default: 1 */ window.plugin.portalslist.fields = [ { title: "Portal Name", value: function(portal) { return portal.options.data.title; }, sortValue: function(value, portal) { return value.toLowerCase(); }, format: function(cell, portal, value) { $(cell) .append(plugin.portalslist.getPortalLink(portal)) .addClass("portalTitle"); } }, { title: "Level", value: function(portal) { return portal.options.data.level; }, format: function(cell, portal, value) { $(cell) .css('background-color', COLORS_LVL[value]) .text('L' + value); }, defaultOrder: -1, }, { title: "Team", value: function(portal) { return portal.options.team; }, format: function(cell, portal, value) { $(cell).text(['NEU', 'RES', 'ENL'][value]); } }, { title: "Health", value: function(portal) { return portal.options.data.health; }, sortValue: function(value, portal) { return portal.options.team===TEAM_NONE ? -1 : value; }, format: function(cell, portal, value) { $(cell) .addClass("alignR") .text(portal.options.team===TEAM_NONE ? '-' : value+'%'); }, defaultOrder: -1, }, { title: "Res", value: function(portal) { return portal.options.data.resCount; }, format: function(cell, portal, value) { $(cell) .addClass("alignR") .text(value); }, defaultOrder: -1, }, { title: "Links", value: function(portal) { return window.getPortalLinks(portal.options.guid); }, sortValue: function(value, portal) { return value.in.length + value.out.length; }, format: function(cell, portal, value) { $(cell) .addClass("alignR") .addClass('help') .attr('title', 'In:\t' + value.in.length + '\nOut:\t' + value.out.length) .text(value.in.length+value.out.length); }, defaultOrder: -1, }, { title: "Fields", value: function(portal) { return getPortalFieldsCount(portal.options.guid) }, format: function(cell, portal, value) { $(cell) .addClass("alignR") .text(value); }, defaultOrder: -1, }, { title: "AP", value: function(portal) { var links = window.getPortalLinks(portal.options.guid); var fields = getPortalFieldsCount(portal.options.guid); return portalApGainMaths(portal.options.data.resCount, links.in.length+links.out.length, fields); }, sortValue: function(value, portal) { return value.enemyAp; }, format: function(cell, portal, value) { var title = ''; if (teamStringToId(PLAYER.team) == portal.options.team) { title += 'Friendly AP:\t'+value.friendlyAp+'\n' + '- deploy '+(8-portal.options.data.resCount)+' resonator(s)\n' + '- upgrades/mods unknown\n'; } title += 'Enemy AP:\t'+value.enemyAp+'\n' + '- Destroy AP:\t'+value.destroyAp+'\n' + '- Capture AP:\t'+value.captureAp; $(cell) .addClass("alignR") .addClass('help') .prop('title', title) .html(digits(value.enemyAp)); }, defaultOrder: -1, }, ]; //fill the listPortals array with portals avaliable on the map (level filtered portals will not appear in the table) window.plugin.portalslist.getPortals = function() { //filter : 0 = All, 1 = Neutral, 2 = Res, 3 = Enl, -x = all but x var retval=false; var displayBounds = map.getBounds(); window.plugin.portalslist.listPortals = []; $.each(window.portals, function(i, portal) { // eliminate offscreen portals (selected, and in padding) if(!displayBounds.contains(portal.getLatLng())) return true; retval=true; switch (portal.options.team) { case TEAM_RES: window.plugin.portalslist.resP++; break; case TEAM_ENL: window.plugin.portalslist.enlP++; break; default: window.plugin.portalslist.neuP++; } // cache values and DOM nodes var obj = { portal: portal, values: [], sortValues: [] }; var row = document.createElement('tr'); row.className = TEAM_TO_CSS[portal.options.team]; obj.row = row; var cell = row.insertCell(-1); cell.className = 'alignR'; window.plugin.portalslist.fields.forEach(function(field, i) { cell = row.insertCell(-1); var value = field.value(portal); obj.values.push(value); obj.sortValues.push(field.sortValue ? field.sortValue(value, portal) : value); if(field.format) { field.format(cell, portal, value); } else { cell.textContent = value; } }); window.plugin.portalslist.listPortals.push(obj); }); return retval; } window.plugin.portalslist.displayPL = function() { var list; // plugins (e.g. bookmarks) can insert fields before the standard ones - so we need to search for the 'level' column window.plugin.portalslist.sortBy = window.plugin.portalslist.fields.map(function(f){return f.title;}).indexOf('Level'); window.plugin.portalslist.sortOrder = -1; window.plugin.portalslist.enlP = 0; window.plugin.portalslist.resP = 0; window.plugin.portalslist.neuP = 0; window.plugin.portalslist.filter = 0; if (window.plugin.portalslist.getPortals()) { list = window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy, window.plugin.portalslist.sortOrder,window.plugin.portalslist.filter); } else { list = $('
Nothing to show!
'); }; if(window.useAndroidPanes()) { $('
').append(list).appendTo(document.body); } else { dialog({ html: $('
').append(list), dialogClass: 'ui-dialog-portalslist', title: 'Portal list: ' + window.plugin.portalslist.listPortals.length + ' ' + (window.plugin.portalslist.listPortals.length == 1 ? 'portal' : 'portals'), id: 'portal-list', width: 700 }); } } window.plugin.portalslist.portalTable = function(sortBy, sortOrder, filter) { // save the sortBy/sortOrder/filter window.plugin.portalslist.sortBy = sortBy; window.plugin.portalslist.sortOrder = sortOrder; window.plugin.portalslist.filter = filter; var portals = window.plugin.portalslist.listPortals; var sortField = window.plugin.portalslist.fields[sortBy]; portals.sort(function(a, b) { var valueA = a.sortValues[sortBy]; var valueB = b.sortValues[sortBy]; if(sortField.sort) { return sortOrder * sortField.sort(valueA, valueB, a.portal, b.portal); } //FIXME: sort isn't stable, so re-sorting identical values can change the order of the list. //fall back to something constant (e.g. portal name?, portal GUID?), //or switch to a stable sort so order of equal items doesn't change return sortOrder * (valueA < valueB ? -1 : valueA > valueB ? 1 : 0); }); if(filter !== 0) { portals = portals.filter(function(obj) { return filter < 0 ? obj.portal.options.team+1 != -filter : obj.portal.options.team+1 == filter; }); } var table, row, cell; var container = $('
'); table = document.createElement('table'); table.className = 'filter'; container.append(table); row = table.insertRow(-1); var length = window.plugin.portalslist.listPortals.length; ["All", "Neutral", "Resistance", "Enlightened"].forEach(function(label, i) { cell = row.appendChild(document.createElement('th')); cell.className = 'filter' + label.substr(0, 3); cell.textContent = label+':'; cell.title = 'Show only portals of this color'; $(cell).click(function() { $('#portalslist').empty().append(window.plugin.portalslist.portalTable(sortBy, sortOrder, i)); }); cell = row.insertCell(-1); cell.className = 'filter' + label.substr(0, 3); if(i != 0) cell.title = 'Hide portals of this color'; $(cell).click(function() { $('#portalslist').empty().append(window.plugin.portalslist.portalTable(sortBy, sortOrder, -i)); }); switch(i-1) { case -1: cell.textContent = length; break; case 0: cell.textContent = window.plugin.portalslist.neuP + ' (' + Math.round(window.plugin.portalslist.neuP/length*100) + '%)'; break; case 1: cell.textContent = window.plugin.portalslist.resP + ' (' + Math.round(window.plugin.portalslist.resP/length*100) + '%)'; break; case 2: cell.textContent = window.plugin.portalslist.enlP + ' (' + Math.round(window.plugin.portalslist.enlP/length*100) + '%)'; } }); table = document.createElement('table'); table.className = 'portals'; container.append(table); var thead = table.appendChild(document.createElement('thead')); row = thead.insertRow(-1); cell = row.appendChild(document.createElement('th')); cell.textContent = '#'; window.plugin.portalslist.fields.forEach(function(field, i) { cell = row.appendChild(document.createElement('th')); cell.textContent = field.title; if(field.sort !== null) { cell.classList.add("sortable"); if(i == window.plugin.portalslist.sortBy) { cell.classList.add("sorted"); } $(cell).click(function() { var order; if(i == sortBy) { order = -sortOrder; } else { order = field.defaultOrder < 0 ? -1 : 1; } $('#portalslist').empty().append(window.plugin.portalslist.portalTable(i, order, filter)); }); } }); portals.forEach(function(obj, i) { var row = obj.row if(row.parentNode) row.parentNode.removeChild(row); row.cells[0].textContent = i+1; table.appendChild(row); }); container.append('
Click on portals table headers to sort by that column. ' + 'Click on All, Neutral, Resistance, Enlightened to only show portals owner by that faction or on the number behind the factions to show all but those portals.
'); return container; } // portal link - single click: select portal // double click: zoom to and select portal // code from getPortalLink function by xelio from iitc: AP List - https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/plugins/ap-list.user.js window.plugin.portalslist.getPortalLink = function(portal) { var coord = portal.getLatLng(); var perma = '/intel?ll='+coord.lat+','+coord.lng+'&z=17&pll='+coord.lat+','+coord.lng; // jQuery's event handlers seem to be removed when the nodes are remove from the DOM var link = document.createElement("a"); link.textContent = portal.options.data.title; link.href = perma; link.addEventListener("click", function(ev) { renderPortalDetails(portal.options.guid); ev.preventDefault(); return false; }, false); link.addEventListener("dblclick", function(ev) { zoomToAndShowPortal(portal.options.guid, [coord.lat, coord.lng]); ev.preventDefault(); return false; }); return link; } window.plugin.portalslist.onPaneChanged = function(pane) { if(pane == "plugin-portalslist") window.plugin.portalslist.displayPL(); else $("#portalslist").remove() }; var setup = function() { if(window.useAndroidPanes()) { android.addPane("plugin-portalslist", "Portals list", "ic_action_paste"); addHook("paneChanged", window.plugin.portalslist.onPaneChanged); } else { $('#toolbox').append('Portals list'); } $("