From 5b1e8ec0008ba660a76c0e424eb5d1d55e566e2c Mon Sep 17 00:00:00 2001 From: fkloft Date: Thu, 28 Aug 2014 20:07:12 +0200 Subject: [PATCH] [portals-list] rewrite large parts, making it more extensible --- plugins/portals-list.css | 96 ++++++++ plugins/portals-list.user.js | 456 ++++++++++++++++++++--------------- 2 files changed, 364 insertions(+), 188 deletions(-) create mode 100644 plugins/portals-list.css diff --git a/plugins/portals-list.css b/plugins/portals-list.css new file mode 100644 index 00000000..2abbc70e --- /dev/null +++ b/plugins/portals-list.css @@ -0,0 +1,96 @@ +#portalslist.mobile { + background: transparent; + border: 0 none !important; + height: 100% !important; + width: 100% !important; + left: 0 !important; + top: 0 !important; + position: absolute; + overflow: auto; +} + +#portalslist table { + margin-top: 5px; + border-collapse: collapse; + empty-cells: show; + width: 100%; + clear: both; +} + +#portalslist table td, #portalslist table th { + background-color: #1b415e; + border-bottom: 1px solid #0b314e; + color: white; + padding: 3px; +} + +#portalslist table th { + text-align: center; +} + +#portalslist table .alignR { + text-align: right; +} + +#portalslist table.portals td { + white-space: nowrap; +} + +#portalslist table th.sortable { + cursor: pointer; +} + +#portalslist table .portalTitle { + min-width: 120px !important; + max-width: 240px !important; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +#portalslist .sorted { + color: #FFCE00; +} + +#portalslist table.filter { + table-layout: fixed; + cursor: pointer; + border-collapse: separate; + border-spacing: 1px; +} + +#portalslist table.filter th { + text-align: left; + padding-left: 0.3em; + overflow: hidden; + text-overflow: ellipsis; +} + +#portalslist table.filter td { + text-align: right; + padding-right: 0.3em; + overflow: hidden; + text-overflow: ellipsis; +} + +#portalslist .filterNeu { + background-color: #666; +} + +#portalslist table tr.res td, #portalslist .filterRes { + background-color: #005684; +} + +#portalslist table tr.enl td, #portalslist .filterEnl { + background-color: #017f01; +} + +#portalslist table tr.none td { + background-color: #000; +} + +#portalslist .disclaimer { + margin-top: 10px; + font-size: 10px; +} + diff --git a/plugins/portals-list.user.js b/plugins/portals-list.user.js index 5116102f..85ab67ef 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.1.2.@@DATETIMEVERSION@@ +// @version 0.2.0.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -18,42 +18,133 @@ // PLUGIN START //////////////////////////////////////////////////////// - /* whatsnew - * 0.1.0 : Using the new data format - * 0.0.15: Add 'age' column to display how long each portal has been controlled by its current owner. - * 0.0.14: Add support to new mods (S:Shield - T:Turret - LA:Link Amp - H:Heat-sink - M:Multi-hack - FA:Force Amp) - * 0.0.12: Use dialog() instead of alert so the user can drag the box around - * 0.0.11: Add nominal energy column and # links, fix sort bug when opened even amounts of times, nits - * 0.0.10: Fixed persistent css problem with alert - * 0.0.9 : bugs hunt - * 0.0.8 : Aborted to avoid problems with Niantic (export portals informations as csv or kml file) - * 0.0.7 : more informations available via tooltips (who deployed, energy, ...), new E/AP column - * 0.0.6 : Add power charge information into a new column + bugfix - * 0.0.5 : Filter portals by clicking on 'All portals', 'Res Portals' or 'Enl Portals' - * 0.0.4 : Add link to portals name, one click to display full information in portal panel, double click to zoom on portal, hover to show address - * 0.0.3 : sorting ascending/descending and add numbers of portals by faction on top on table - * 0.0.2 : add sorting feature when click on header column - * 0.0.1 : initial release, show list of portals with level, team, resonators and shield information - * - * Display code inspired from @vita10gy's scoreboard plugin : iitc-plugin-scoreboard@vita10gy - https://github.com/breunigs/ingress-intel-total-conversion - * Portal link code from xelio - iitc: AP List - https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/plugins/ap-list.user.js - * - * todo : export as GPX, Open in Google Maps, more statistics in the header, what else ? - */ - // use own namespace for plugin window.plugin.portalslist = function() {}; window.plugin.portalslist.listPortals = []; -window.plugin.portalslist.sortBy = 'level'; +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. + * 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+'%'); + } + }, + { + title: "Res", + value: function(portal) { return portal.options.data.resCount; }, + format: function(cell, portal, value) { + $(cell) + .addClass("alignR") + .text(value); + } + }, + { + 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); + } + }, + { + title: "Fields", + value: function(portal) { return getPortalFieldsCount(portal.options.guid) }, + format: function(cell, portal, value) { + $(cell) + .addClass("alignR") + .text(value); + } + }, + { + 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 (PLAYER.team == portal.options.data.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)); + } + }, +]; + //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 = Res, 2 = Enl + //filter : 0 = All, 1 = Neutral, 2 = Res, 3 = Enl, -x = all but x var retval=false; var displayBounds = map.getBounds(); @@ -64,64 +155,69 @@ window.plugin.portalslist.getPortals = function() { if(!displayBounds.contains(portal.getLatLng())) return true; retval=true; - var d = portal.options.data; - var teamN = portal.options.team; - switch (teamN) { + 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++; } - var l = window.getPortalLinks(i); - var f = window.getPortalFields(i); - var ap = portalApGainMaths(d.resCount, l.in.length+l.out.length, f.length); - var thisPortal = { - 'portal': portal, - 'guid': i, - 'teamN': teamN, // TEAM_NONE, TEAM_RES or TEAM_ENL - 'team': d.team, // "NEUTRAL", "RESISTANCE" or "ENLIGHTENED" - 'name': d.title || '(untitled)', - 'nameLower': d.title && d.title.toLowerCase(), - 'level': portal.options.level, - 'health': d.health, - 'resCount': d.resCount, - 'img': d.img, - 'linkCount': l.in.length + l.out.length, - 'link' : l, - 'fieldCount': f.length, - 'field' : f, - 'enemyAp': ap.enemyAp, - 'ap': ap, - }; - window.plugin.portalslist.listPortals.push(thisPortal); + // 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 html = ''; - window.plugin.portalslist.sortBy = 'level'; + var list; + window.plugin.portalslist.sortBy = 1; 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()) { - html += window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy, window.plugin.portalslist.sortOrder,window.plugin.portalslist.filter); + list = window.plugin.portalslist.portalTable(window.plugin.portalslist.sortBy, window.plugin.portalslist.sortOrder,window.plugin.portalslist.filter); } else { - html = '
Nothing to show!
'; + list = $('
Nothing to show!
'); }; if(window.useAndroidPanes()) { - $('
' + html + '
').appendTo(document.body); + $('
').append(list).appendTo(document.body); } else { dialog({ - html: '
' + html + '
', + 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', @@ -136,124 +232,142 @@ window.plugin.portalslist.portalTable = function(sortBy, sortOrder, filter) { window.plugin.portalslist.sortOrder = sortOrder; window.plugin.portalslist.filter = filter; - var portals=window.plugin.portalslist.listPortals; + var portals = window.plugin.portalslist.listPortals; + var sortField = window.plugin.portalslist.fields[sortBy]; - //Array sort - window.plugin.portalslist.listPortals.sort(function(a, b) { - var retVal = 0; + portals.sort(function(a, b) { + var valueA = a.sortValues[sortBy]; + var valueB = b.sortValues[sortBy]; - var aComp = a[sortBy]; - var bComp = b[sortBy]; - - if (aComp < bComp) { - retVal = -1; - } else if (aComp > bComp) { - retVal = 1; - } else { - // equal - compare GUIDs to ensure consistent (but arbitrary) order - retVal = a.guid < b.guid ? -1 : 1; + if(sortField.sort) { + return sortOrder * sortField.sort(valueA, valueB, a.portal, b.portal); } - // sortOrder is 1 (normal) or -1 (reversed) - retVal = retVal * sortOrder; - return retVal; + return sortOrder * + (valueA < valueB ? -1 : + valueA > valueB ? 1 : + 0); }); - var sortAttr = window.plugin.portalslist.portalTableHeaderSortAttr; - var html = window.plugin.portalslist.stats(); - html += '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '\n'; + if(filter !== 0) { + portals = portals.filter(function(obj) { + return filter < 0 + ? obj.portal.options.team+1 != -filter + : obj.portal.options.team+1 == filter; + }); + } - var rowNum = 1; + var table, row, cell; + var container = $('
'); - $.each(portals, function(ind, portal) { - if (filter === TEAM_NONE || filter === portal.teamN) { + table = document.createElement('table'); + table.className = 'filter'; + container.append(table); - html += '
' - + '' - + '' - + '' - + ''; + row = table.insertRow(-1); - html += '' - + '' - + '' - + ''; + var length = window.plugin.portalslist.listPortals.length; - var apTitle = ''; - if (PLAYER.team == portal.team) { - apTitle += 'Friendly AP:\t'+portal.ap.friendlyAp+'\n' - + '- deploy '+(8-portal.resCount)+' resonator(s)\n' - + '- upgrades/mods unknown\n'; + ["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"); } - apTitle += 'Enemy AP:\t'+portal.ap.enemyAp+'\n' - + '- Destroy AP:\t'+portal.ap.destroyAp+'\n' - + '- Capture AP:\t'+portal.ap.captureAp; - html += ''; + $(cell).click(function() { + var order; + if(i == sortBy) { + order = -sortOrder; + } else { + order = field.defaultOrder < 0 ? -1 : 1; + } - html+= ''; - - rowNum++; + $('#portalslist').empty().append(window.plugin.portalslist.portalTable(i, order, filter)); + }); } }); - html += '
#Portal NameLevelTeamHealthResLinksFieldsAP
'+rowNum+'' + window.plugin.portalslist.getPortalLink(portal, portal.guid) + '' + portal.level + '' + portal.team.substr(0,3) + '' + (portal.teamN!=TEAM_NONE?portal.health+'%':'-') + '' + portal.resCount + '' + (portal.linkCount?portal.linkCount:'-') + '' + (portal.fieldCount?portal.fieldCount:'-') + '' + digits(portal.ap.enemyAp) + '
'; - html += '
Click on portals table headers to sort by that column. ' - + 'Click on All Portals, Resistance Portals, Enlightened Portals to filter
'; + portals.forEach(function(obj, i) { + var row = obj.row + if(row.parentNode) row.parentNode.removeChild(row); - return html; + 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; } -window.plugin.portalslist.stats = function(sortBy) { - var html = '' - + '' - + '' - + '' - + '' - + '
All Portals : (click to filter)' + window.plugin.portalslist.listPortals.length + 'Resistance Portals : ' + window.plugin.portalslist.resP +' (' + Math.floor(window.plugin.portalslist.resP/window.plugin.portalslist.listPortals.length*100) + '%)Enlightened Portals : '+ window.plugin.portalslist.enlP +' (' + Math.floor(window.plugin.portalslist.enlP/window.plugin.portalslist.listPortals.length*100) + '%)
'; - return html; -} - -// A little helper function so the above isn't so messy -window.plugin.portalslist.portalTableHeaderSortAttr = function(name, by, defOrder, extraClass) { - // data-sort attr: used by jquery .data('sort') below - var retVal = 'data-sort="'+name+'" data-defaultorder="'+defOrder+'" class="'+(extraClass?extraClass+' ':'')+'sortable'+(name==by?' sorted':'')+'"'; - - return retVal; -}; - // portal link - single click: select portal // double click: zoom to and select portal -// hover: show address // 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,guid) { - var coord = portal.portal.getLatLng(); - var latlng = [coord.lat, coord.lng].join(); - var jsSingleClick = 'window.renderPortalDetails(\''+guid+'\');return false'; - var jsDoubleClick = 'window.zoomToAndShowPortal(\''+guid+'\', ['+latlng+']);return false'; +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; - //Use Jquery to create the link, which escape characters in TITLE and ADDRESS of portal - var a = $('',{ - text: portal.name, -// title: portal.name, - href: perma, - onClick: jsSingleClick, - onDblClick: jsDoubleClick - })[0].outerHTML; - - return a; + //Use Jquery to create the link, which escape characters in TITLE of portal + return $('') + .text(portal.options.data.title) + .attr('href', perma) + .click(function(ev) { + renderPortalDetails(portal.options.guid); + ev.preventDefault(); + return false; + }) + .dblclick(function(ev) { + zoomToAndShowPortal(portal.options.guid, [coord.lat, coord.lng]); + ev.preventDefault(); + return false; + }); } window.plugin.portalslist.onPaneChanged = function(pane) { @@ -271,45 +385,11 @@ var setup = function() { $('#toolbox').append(' Portals list'); } - $('head').append(''); + $("