Advertisement
Guest User

iitc-plugin-portals-list script with export (w/ bugfixes)

a guest
Jan 11th, 2018
6,576
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @id             iitc-plugin-portals-list@teo96
  3. // @name           IITC plugin: show list of portals // modified with export button
  4. // @category       Info
  5. // @version        0.2.1.20170108.21732
  6. // @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
  7. // @updateURL      https://static.iitc.me/build/release/plugins/portals-list.meta.js
  8. // @downloadURL    https://static.iitc.me/build/release/plugins/portals-list.user.js
  9. // @description    [iitc-2017-01-08-021732] Display a sortable list of all visible portals with full details about the team, resonators, links, etc.
  10. // @include        https://*.ingress.com/intel*
  11. // @include        http://*.ingress.com/intel*
  12. // @match          https://*.ingress.com/intel*
  13. // @match          http://*.ingress.com/intel*
  14. // @include        https://*.ingress.com/mission/*
  15. // @include        http://*.ingress.com/mission/*
  16. // @match          https://*.ingress.com/mission/*
  17. // @match          http://*.ingress.com/mission/*
  18. // @grant          none
  19. // ==/UserScript==
  20.  
  21.  
  22. function wrapper(plugin_info) {
  23. // ensure plugin framework is there, even if iitc is not yet loaded
  24. if(typeof window.plugin !== 'function') window.plugin = function() {};
  25.  
  26. //PLUGIN AUTHORS: writing a plugin outside of the IITC build environment? if so, delete these lines!!
  27. //(leaving them in place might break the 'About IITC' page or break update checks)
  28. plugin_info.buildName = 'iitc';
  29. plugin_info.dateTimeVersion = '20170108.21732';
  30. plugin_info.pluginId = 'portals-list';
  31. //END PLUGIN AUTHORS NOTE
  32.  
  33.  
  34.  
  35. // PLUGIN START ////////////////////////////////////////////////////////
  36.  
  37. // use own namespace for plugin
  38. window.plugin.portalslist = function() {};
  39.  
  40. window.plugin.portalslist.listPortals = [];
  41. window.plugin.portalslist.sortBy = 1; // second column: level
  42. window.plugin.portalslist.sortOrder = -1;
  43. window.plugin.portalslist.enlP = 0;
  44. window.plugin.portalslist.resP = 0;
  45. window.plugin.portalslist.neuP = 0;
  46. window.plugin.portalslist.filter = 0;
  47.  
  48. /*
  49.  * plugins may add fields by appending their specifiation to the following list. The following members are supported:
  50.  * title: String
  51.  *     Name of the column. Required.
  52.  * value: function(portal)
  53.  *     The raw value of this field. Can by anything. Required, but can be dummy implementation if sortValue and format
  54.  *     are implemented.
  55.  * sortValue: function(value, portal)
  56.  *     The value to sort by. Optional, uses value if omitted. The raw value is passed as first argument.
  57.  * sort: function(valueA, valueB, portalA, portalB)
  58.  *     Custom sorting function. See Array.sort() for details on return value. Both the raw values and the portal objects
  59.  *     are passed as arguments. Optional. Set to null to disable sorting
  60.  * format: function(cell, portal, value)
  61.  *     Used to fill and format the cell, which is given as a DOM node. If omitted, the raw value is put in the cell.
  62.  * defaultOrder: -1|1
  63.  *     Which order should by default be used for this column. -1 means descending. Default: 1
  64.  */
  65.  
  66.  
  67. window.plugin.portalslist.fields = [
  68.   {
  69.     title: "Portal Name",
  70.     value: function(portal) { return portal.options.data.title; },
  71.     sortValue: function(value, portal) { return value.toLowerCase(); },
  72.     format: function(cell, portal, value) {
  73.       $(cell)
  74.         .append(plugin.portalslist.getPortalLink(portal))
  75.         .addClass("portalTitle");
  76.     }
  77.   },
  78.   {
  79.     title: "Latitude",
  80.     value: function(portal) { return portal.getLatLng().lat; },
  81.     format: function(cell, portal, value) {
  82.       $(cell).text(value);
  83.     }
  84.   },
  85.   {
  86.     title: "Longitude",
  87.     value: function(portal) { return portal.getLatLng().lng; },
  88.     format: function(cell, portal, value) {
  89.       $(cell).text(value);
  90.     }
  91.   },
  92.   {
  93.     title: "Level",
  94.     value: function(portal) { return portal.options.data.level; },
  95.     format: function(cell, portal, value) {
  96.       $(cell)
  97.         .css('background-color', COLORS_LVL[value])
  98.         .text('L' + value);
  99.     },
  100.     defaultOrder: -1,
  101.   },
  102.   {
  103.     title: "Team",
  104.     value: function(portal) { return portal.options.team; },
  105.     format: function(cell, portal, value) {
  106.       $(cell).text(['NEU', 'RES', 'ENL'][value]);
  107.     }
  108.   },
  109.   {
  110.     title: "Health",
  111.     value: function(portal) { return portal.options.data.health; },
  112.     sortValue: function(value, portal) { return portal.options.team===TEAM_NONE ? -1 : value; },
  113.     format: function(cell, portal, value) {
  114.       $(cell)
  115.         .addClass("alignR")
  116.         .text(portal.options.team===TEAM_NONE ? '-' : value+'%');
  117.     },
  118.     defaultOrder: -1,
  119.   },
  120.   {
  121.     title: "Res",
  122.     value: function(portal) { return portal.options.data.resCount; },
  123.     format: function(cell, portal, value) {
  124.       $(cell)
  125.         .addClass("alignR")
  126.         .text(value);
  127.     },
  128.     defaultOrder: -1,
  129.   },
  130.   {
  131.     title: "Links",
  132.     value: function(portal) { return window.getPortalLinks(portal.options.guid); },
  133.     sortValue: function(value, portal) { return value.in.length + value.out.length; },
  134.     format: function(cell, portal, value) {
  135.       $(cell)
  136.         .addClass("alignR")
  137.         .addClass('help')
  138.         .attr('title', 'In:\t' + value.in.length + '\nOut:\t' + value.out.length)
  139.         .text(value.in.length+value.out.length);
  140.     },
  141.     defaultOrder: -1,
  142.   },
  143.   {
  144.     title: "Fields",
  145.     value: function(portal) { return getPortalFieldsCount(portal.options.guid); },
  146.     format: function(cell, portal, value) {
  147.       $(cell)
  148.         .addClass("alignR")
  149.         .text(value);
  150.     },
  151.     defaultOrder: -1,
  152.   },
  153.   {
  154.     title: "AP",
  155.     value: function(portal) {
  156.       var links = window.getPortalLinks(portal.options.guid);
  157.       var fields = getPortalFieldsCount(portal.options.guid);
  158.       return portalApGainMaths(portal.options.data.resCount, links.in.length+links.out.length, fields);
  159.     },
  160.     sortValue: function(value, portal) { return value.enemyAp; },
  161.     format: function(cell, portal, value) {
  162.       var title = '';
  163.       if (teamStringToId(PLAYER.team) == portal.options.team) {
  164.         title += 'Friendly AP:\t'+value.friendlyAp+'\n' + '- deploy '+(8-portal.options.data.resCount)+' resonator(s)\n' + '- upgrades/mods unknown\n';
  165.       }
  166.       title += 'Enemy AP:\t'+value.enemyAp+'\n' + '- Destroy AP:\t'+value.destroyAp+'\n' + '- Capture AP:\t'+value.captureAp;
  167.  
  168.       $(cell)
  169.         .addClass("alignR")
  170.         .addClass('help')
  171.         .prop('title', title)
  172.         .html(digits(value.enemyAp));
  173.     },
  174.     defaultOrder: -1,
  175.   },
  176. ];
  177.  
  178. //fill the listPortals array with portals avaliable on the map (level filtered portals will not appear in the table)
  179. window.plugin.portalslist.getPortals = function() {
  180.   //filter : 0 = All, 1 = Neutral, 2 = Res, 3 = Enl, -x = all but x
  181.   var retval=false;
  182.  
  183.   var displayBounds = map.getBounds();
  184.  
  185.   window.plugin.portalslist.listPortals = [];
  186.   $.each(window.portals, function(i, portal) {
  187.     // eliminate offscreen portals (selected, and in padding)
  188.     if(!displayBounds.contains(portal.getLatLng())) return true;
  189.  
  190.     retval=true;
  191.  
  192.     switch (portal.options.team) {
  193.       case TEAM_RES:
  194.         window.plugin.portalslist.resP++;
  195.         break;
  196.       case TEAM_ENL:
  197.         window.plugin.portalslist.enlP++;
  198.         break;
  199.       default:
  200.         window.plugin.portalslist.neuP++;
  201.     }
  202.  
  203.     // cache values and DOM nodes
  204.     var obj = { portal: portal, values: [], sortValues: [] };
  205.  
  206.     var row = document.createElement('tr');
  207.     row.className = TEAM_TO_CSS[portal.options.team];
  208.     obj.row = row;
  209.  
  210.     var cell = row.insertCell(-1);
  211.     cell.className = 'alignR';
  212.  
  213.     window.plugin.portalslist.fields.forEach(function(field, i) {
  214.       cell = row.insertCell(-1);
  215.  
  216.       var value = field.value(portal);
  217.       obj.values.push(value);
  218.  
  219.       //console.log('DEBUG2 ' + value);
  220.       obj.sortValues.push(field.sortValue ? field.sortValue(value, portal) : value);
  221.  
  222.       if(field.format) {
  223.         field.format(cell, portal, value);
  224.       } else {
  225.         cell.textContent = value;
  226.       }
  227.     });
  228.  
  229.     window.plugin.portalslist.listPortals.push(obj);
  230.   });
  231.  
  232.   return retval;
  233. };
  234.  
  235. window.plugin.portalslist.displayPL = function() {
  236.   var list;
  237.   // plugins (e.g. bookmarks) can insert fields before the standard ones - so we need to search for the 'level' column
  238.   window.plugin.portalslist.sortBy = window.plugin.portalslist.fields.map(function(f){return f.title;}).indexOf('Level');
  239.   window.plugin.portalslist.sortOrder = -1;
  240.   window.plugin.portalslist.enlP = 0;
  241.   window.plugin.portalslist.resP = 0;
  242.   window.plugin.portalslist.neuP = 0;
  243.   window.plugin.portalslist.filter = 0;
  244.  
  245.   if (window.plugin.portalslist.getPortals()) {
  246.     list = window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy, window.plugin.portalslist.sortOrder,window.plugin.portalslist.filter);
  247.   } else {
  248.     list = $('<table class="noPortals"><tr><td>Nothing to show!</td></tr></table>');
  249.   }
  250.  
  251.   if(window.useAndroidPanes()) {
  252.     $('<div id="portalslist" class="mobile">').append(list).appendTo(document.body);
  253.   } else {
  254.     dialog({
  255.       html: $('<div id="portalslist">').append(list),
  256.       dialogClass: 'ui-dialog-portalslist',
  257.       title: 'Portal list: ' + window.plugin.portalslist.listPortals.length + ' ' + (window.plugin.portalslist.listPortals.length == 1 ? 'portal' : 'portals'),
  258.       id: 'portal-list',
  259.       width: 700
  260.     });
  261.   }
  262. };
  263.  
  264. window.plugin.portalslist.portalTable = function(sortBy, sortOrder, filter) {
  265.   // save the sortBy/sortOrder/filter
  266.   window.plugin.portalslist.sortBy = sortBy;
  267.   window.plugin.portalslist.sortOrder = sortOrder;
  268.   window.plugin.portalslist.filter = filter;
  269.  
  270.   var portals = window.plugin.portalslist.listPortals;
  271.   var sortField = window.plugin.portalslist.fields[sortBy];
  272.  
  273.   portals.sort(function(a, b) {
  274.     var valueA = a.sortValues[sortBy];
  275.     var valueB = b.sortValues[sortBy];
  276.  
  277.     if(sortField.sort) {
  278.       return sortOrder * sortField.sort(valueA, valueB, a.portal, b.portal);
  279.     }
  280.  
  281. //FIXME: sort isn't stable, so re-sorting identical values can change the order of the list.
  282. //fall back to something constant (e.g. portal name?, portal GUID?),
  283. //or switch to a stable sort so order of equal items doesn't change
  284.     return sortOrder *
  285.       (valueA < valueB ? -1 :
  286.       valueA > valueB ?  1 :
  287.       0);
  288.   });
  289.  
  290.   if(filter !== 0) {
  291.     portals = portals.filter(function(obj) {
  292.       return filter < 0 ? obj.portal.options.team+1 != -filter : obj.portal.options.team+1 == filter;
  293.     });
  294.   }
  295.  
  296.   var table, row, cell;
  297.   var container = $('<div>');
  298.  
  299.   table = document.createElement('table');
  300.   table.className = 'filter';
  301.   container.append(table);
  302.  
  303.   row = table.insertRow(-1);
  304.  
  305.   var length = window.plugin.portalslist.listPortals.length;
  306.  
  307.   ["All", "Neutral", "Resistance", "Enlightened"].forEach(function(label, i) {
  308.     cell = row.appendChild(document.createElement('th'));
  309.     cell.className = 'filter' + label.substr(0, 3);
  310.     cell.textContent = label+':';
  311.     cell.title = 'Show only portals of this color';
  312.     $(cell).click(function() {
  313.       $('#portalslist').empty().append(window.plugin.portalslist.portalTable(sortBy, sortOrder, i));
  314.     });
  315.  
  316.  
  317.     cell = row.insertCell(-1);
  318.     cell.className = 'filter' + label.substr(0, 3);
  319.     if(i != 0) cell.title = 'Hide portals of this color';
  320.     $(cell).click(function() {
  321.       $('#portalslist').empty().append(window.plugin.portalslist.portalTable(sortBy, sortOrder, -i));
  322.     });
  323.  
  324.     switch(i-1) {
  325.       case -1:
  326.         cell.textContent = length;
  327.         break;
  328.       case 0:
  329.         cell.textContent = window.plugin.portalslist.neuP + ' (' + Math.round(window.plugin.portalslist.neuP/length*100) + '%)';
  330.         break;
  331.       case 1:
  332.         cell.textContent = window.plugin.portalslist.resP + ' (' + Math.round(window.plugin.portalslist.resP/length*100) + '%)';
  333.         break;
  334.       case 2:
  335.         cell.textContent = window.plugin.portalslist.enlP + ' (' + Math.round(window.plugin.portalslist.enlP/length*100) + '%)';
  336.     }
  337.   });
  338.  
  339.   table = document.createElement('table');
  340.   table.className = 'portals';
  341.   container.append(table);
  342.  
  343.   var thead = table.appendChild(document.createElement('thead'));
  344.   row = thead.insertRow(-1);
  345.  
  346.   cell = row.appendChild(document.createElement('th'));
  347.   cell.textContent = '#';
  348.  
  349.   window.plugin.portalslist.fields.forEach(function(field, i) {
  350.     cell = row.appendChild(document.createElement('th'));
  351.     cell.textContent = field.title;
  352.     if(field.sort !== null) {
  353.       cell.classList.add("sortable");
  354.       if(i == window.plugin.portalslist.sortBy) {
  355.         cell.classList.add("sorted");
  356.       }
  357.  
  358.       $(cell).click(function() {
  359.         var order;
  360.         if(i == sortBy) {
  361.           order = -sortOrder;
  362.         } else {
  363.           order = field.defaultOrder < 0 ? -1 : 1;
  364.         }
  365.  
  366.         $('#portalslist').empty().append(window.plugin.portalslist.portalTable(i, order, filter));
  367.       });
  368.     }
  369.   });
  370.  
  371.   portals.forEach(function(obj, i) {
  372.     var row = obj.row;
  373.     if(row.parentNode) row.parentNode.removeChild(row);
  374.  
  375.     row.cells[0].textContent = i+1;
  376.  
  377.     table.appendChild(row);
  378.   });
  379.  
  380.   container.append('<div class="disclaimer">Click on portals table headers to sort by that column. ' + 'Click on <b>All, Neutral, Resistance, Enlightened</b> to only show portals owner by that faction or on the number behind the factions to show all but those portals.</div>');
  381.  
  382.   container.append('<div class="disclaimer"><a onclick="window.plugin.portalslist.exportPortals()" title="Export a list of portals in the current view [e]" accesskey="e">Export portals</a>.</div>');
  383.  
  384.   return container;
  385. };
  386.  
  387. window.plugin.portalslist.exportPortals = function() {
  388.   var portals = window.plugin.portalslist.listPortals;
  389.  
  390.   var s = '';
  391.  
  392.   portals.forEach(function(obj, i) {
  393.     s = s + '"' + portals[i].values[0] + '",' + portals[i].values[1] + ',' + portals[i].values[2] + '\n';
  394.   });
  395.  
  396.   var a = window.document.createElement('a');
  397.   a.href = window.URL.createObjectURL(new Blob([s], {type: 'text/csv'}));
  398.   a.download = 'export.csv';
  399.  
  400.   // Append anchor to body.
  401.   document.body.appendChild(a);
  402.   a.click();
  403.  
  404.   // Remove anchor from body
  405.   document.body.removeChild(a);
  406. };
  407.  
  408. // portal link - single click: select portal
  409. //               double click: zoom to and select portal
  410. // 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
  411. window.plugin.portalslist.getPortalLink = function(portal) {
  412.   var coord = portal.getLatLng();
  413.   var perma = '/intel?ll='+coord.lat+','+coord.lng+'&z=17&pll='+coord.lat+','+coord.lng;
  414.  
  415.   // jQuery's event handlers seem to be removed when the nodes are remove from the DOM
  416.   var link = document.createElement("a");
  417.   link.textContent = portal.options.data.title;
  418.   link.href = perma;
  419.   link.addEventListener("click", function(ev) {
  420.     renderPortalDetails(portal.options.guid);
  421.     ev.preventDefault();
  422.     return false;
  423.   }, false);
  424.   link.addEventListener("dblclick", function(ev) {
  425.     zoomToAndShowPortal(portal.options.guid, [coord.lat, coord.lng]);
  426.     ev.preventDefault();
  427.     return false;
  428.   });
  429.   return link;
  430. };
  431.  
  432. window.plugin.portalslist.onPaneChanged = function(pane) {
  433.   if(pane == "plugin-portalslist")
  434.     window.plugin.portalslist.displayPL();
  435.   else
  436.     $("#portalslist").remove();
  437. };
  438.  
  439. var setup =  function() {
  440.   if(window.useAndroidPanes()) {
  441.     android.addPane("plugin-portalslist", "Portals list", "ic_action_paste");
  442.     addHook("paneChanged", window.plugin.portalslist.onPaneChanged);
  443.   } else {
  444.     $('#toolbox').append('<a onclick="window.plugin.portalslist.displayPL()" title="Display a list of portals in the current view [t]" accesskey="t">Portals list</a>');
  445.   }
  446.  
  447.   $("<style>")
  448.     .prop("type", "text/css")
  449.     .html("#portalslist.mobile {\n  background: transparent;\n  border: 0 none !important;\n  height: 100% !important;\n  width: 100% !important;\n  left: 0 !important;\n  top: 0 !important;\n  position: absolute;\n  overflow: auto;\n}\n\n#portalslist table {\n  margin-top: 5px;\n  border-collapse: collapse;\n  empty-cells: show;\n  width: 100%;\n  clear: both;\n}\n\n#portalslist table td, #portalslist table th {\n  background-color: #1b415e;\n  border-bottom: 1px solid #0b314e;\n  color: white;\n  padding: 3px;\n}\n\n#portalslist table th {\n  text-align: center;\n}\n\n#portalslist table .alignR {\n  text-align: right;\n}\n\n#portalslist table.portals td {\n  white-space: nowrap;\n}\n\n#portalslist table th.sortable {\n  cursor: pointer;\n}\n\n#portalslist table .portalTitle {\n  min-width: 120px !important;\n  max-width: 240px !important;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n#portalslist .sorted {\n  color: #FFCE00;\n}\n\n#portalslist table.filter {\n  table-layout: fixed;\n  cursor: pointer;\n  border-collapse: separate;\n  border-spacing: 1px;\n}\n\n#portalslist table.filter th {\n  text-align: left;\n  padding-left: 0.3em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n#portalslist table.filter td {\n  text-align: right;\n  padding-right: 0.3em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n#portalslist .filterNeu {\n  background-color: #666;\n}\n\n#portalslist table tr.res td, #portalslist .filterRes {\n  background-color: #005684;\n}\n\n#portalslist table tr.enl td, #portalslist .filterEnl {\n  background-color: #017f01;\n}\n\n#portalslist table tr.none td {\n  background-color: #000;\n}\n\n#portalslist .disclaimer {\n  margin-top: 10px;\n  font-size: 10px;\n}\n\n#portalslist.mobile table.filter tr {\n  display: block;\n  text-align: center;\n}\n#portalslist.mobile table.filter th, #portalslist.mobile table.filter td {\n  display: inline-block;\n  width: 22%;\n}\n\n")
  450.     .appendTo("head");
  451.  
  452. };
  453.  
  454. // PLUGIN END //////////////////////////////////////////////////////////
  455.  
  456.  
  457. setup.info = plugin_info; //add the script info data to the function as a property
  458. if(!window.bootPlugins) window.bootPlugins = [];
  459. window.bootPlugins.push(setup);
  460. // if IITC has already booted, immediately run the 'setup' function
  461. if(window.iitcLoaded && typeof setup === 'function') setup();
  462. } // wrapper end
  463. // inject code into site context
  464. var script = document.createElement('script');
  465. var info = {};
  466. if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description };
  467. script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');'));
  468. (document.body || document.head || document.documentElement).appendChild(script);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement