// ==UserScript==
// @author DanielOnDiordna
// @name Unique Portal History
// @category Layer
// @version 2.2.0.20240525.141200
// @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/uniqueportalhistory.meta.js
// @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/uniqueportalhistory.user.js
// @description [danielondiordna-2.2.0.20240525.141200] Show your personal unique portal history for Visited, Captured or Scout Controlled portals with layers. Choose your own colors for Resistance, Enlightened, Machina and Neutral portals. Place bookmarks. Invert results! Add three extra Portals List plugin columns. Does not require CORE subscription.
// @id uniqueportalhistory@DanielOnDiordna
// @namespace https://softspot.nl/ingress/
// @depends portalhistorysupport@DanielOnDiordna
// @antiFeatures scraper
// @match https://intel.ingress.com/*
// @grant none
// ==/UserScript==
function wrapper(plugin_info) {
// ensure plugin framework is there, even if iitc is not yet loaded
if(typeof window.plugin !== 'function') window.plugin = function() {};
// use own namespace for plugin
window.plugin.uniqueportalhistory = function() {};
var self = window.plugin.uniqueportalhistory;
self.id = 'uniqueportalhistory';
self.title = 'Unique Portal History';
self.version = '2.2.0.20240525.141200';
self.author = 'DanielOnDiordna';
self.changelog = `
Changelog:
version 2.2.0.20240525.141200
- added Machina support
- added a zoom to see all bookmarks button
- improved the bookmark menu and methods
- improved the inverting of overlay layers
version 2.1.0.20220711.235400
- made compatible with IITC-CE Beta 0.32.1.20211217.151857
version 2.0.1.20210724.002500
- prevent double plugin setup on hook iitcLoaded
version 2.0.1.20210517.233500
- added bookmarks anywhere for cached history portals (if plugin portalhistorysupport is installed)
version 2.0.0.20210313.210300
- removed all IITC core injection functions (moved to separate and required Portal History Support plugin)
- removed history storage, this is now cached and returned from Portal History Support plugin
- removed highlighter functions (moved to separate optional History Highlighter plugin)
- removed getPortalHistoryDetails function
- replace variable scanned with scoutControlled
- added rescaling markers when zooming in or out
version 1.0.1.20210222.233700
- rewritten some IITC core injection function
- added decodeArray.portalDetail rewrite, to support new history bitarray
- fixed all portals zoom level detection
version 1.0.0.20210220.134700
- renamed plugin from Portal Agent Status to Unique Portal History
- improved IITC core modifications (needed for older versions of IITC and also for latest version of IITC)
- applied portalAdded code to add history if it is missing (for older versions of IITC)
- added option to keep showing layers when zooming out
- added caching option, which will make it possible to display markers on linked portals without zooming in after reload of IITC and show total counts
- see sortable columns in Portals List plugin
- removed single team color and added both teams, there are now 5 layers to choose from
- added highlighters to hide all or only all captured portals, with or without ENL and RES portals
version 0.0.3.20210211.164200
version 0.0.3.20210211.182800
- fixed log.log debug error on IITC-CE
- added extra code injections
- added same team target color setting
- integrated Spectrum Colorpicker 1.8.1 plugin code, no need for the separate plugin
- bookmarks menu
version 0.0.2.20210209.235900
version 0.0.2.20210210.001100
- a lot of changes, a complete rewrite
- added a menu to select inversion of results
version 0.0.1.20210206.120400
- first release
- used plugin wrapper and userscript header formatting to match IITC-CE coding
`;
self.namespace = 'window.plugin.' + self.id + '.';
self.pluginname = 'plugin-' + self.id;
self.localstoragesettings = self.pluginname + '-settings';
self.settings = {};
self.settings.invertresults = true;
self.settings.showsameteamcolor = true;
self.settings.showneutralcolor = true;
self.settings.color = {
visited: 'purple',
capturedenl: '#FF0000',
capturedres: '#FF0000',
capturedmac: '#FF0000',
capturedneutral: '#000000',
scoutControlled: 'yellow'
};
self.settings.replacebookmarks = true;
self.settings.bookmarkscolor = 'yellow';
self.settings.showwhenzoomedout = false;
self.settings.drawnameless = false;
self.capturedlayers = {
capturedenl: window.TEAM_ENL,
capturedres: window.TEAM_RES,
capturedmac: window.TEAM_MAC || 3,
capturedneutral: window.TEAM_NONE
};
self.bookmarktimerlist = [];
self.bookmarkrestorecolor = undefined;
self.lastportalscale = undefined;
self.markerformatting = {
visited: { stroke: true, color: 'settingscolor', radius: 15.0, weight: 2, opacity: 0.5, fill: false, fillOpacity: 0.0 },
capturedenl: { stroke: true, color: 'portalcolor', radius: 'portalradius', weight: 'portalweight', opacity: 1.0, fill: true, fillOpacity: 1.0, fillColor: 'settingscolor' },
capturedres: { stroke: true, color: 'portalcolor', radius: 'portalradius', weight: 'portalweight', opacity: 1.0, fill: true, fillOpacity: 1.0, fillColor: 'settingscolor' },
capturedmac: { stroke: true, color: 'portalcolor', radius: 'portalradius', weight: 'portalweight', opacity: 1.0, fill: true, fillOpacity: 1.0, fillColor: 'settingscolor' },
capturedneutral: { stroke: true, color: 'portalcolor', radius: 'portalradius', weight: 'portalweight', opacity: 1.0, fill: true, fillOpacity: 1.0, fillColor: 'settingscolor' },
scoutControlled: { stroke: true, color: 'settingscolor', radius: 19.0, weight: 3, opacity: 1.0, fill: false, fillOpacity: 0.0 }
};
self.basiclayers = {
visited: 1,
captured: 1,
scoutControlled: 1
};
self.bookmarkfoldernames = {
visited: 'Visited',
captured: 'Captured',
capturedenl: 'Captured ENL',
capturedres: 'Captured RES',
capturedmac: 'Captured MAC',
capturedneutral: 'Captured Neutral',
scoutControlled: 'Scout Controlled'
};
self.toggle_layernames = {
visited: 'Visited',
capturedenl: 'Captured ENL',
capturedres: 'Captured RES',
capturedmac: 'Captured MAC',
capturedneutral: 'Captured neutral',
scoutControlled: 'Scout Controlled'
};
self.toggle_layernamesinverted = {
visited: 'Never Visited',
capturedenl: 'Never Captured ENL',
capturedres: 'Never Captured RES',
capturedmac: 'Never Captured MAC',
capturedneutral: 'Never Captured neutral',
scoutControlled: 'Not Scout Controlled'
};
self.toggle_layers = {
visited: undefined,
capturedenl: undefined,
capturedres: undefined,
capturedmac: undefined,
capturedneutral: undefined,
scoutControlled: undefined,
};
self.layers = {
visited: undefined,
capturedenl: undefined,
capturedres: undefined,
capturedmac: undefined,
capturedneutral: undefined,
scoutControlled: undefined
};
self.layermarkers = {
visited: {},
capturedenl: {},
capturedres: {},
capturedmac: {},
capturedneutral: {},
scoutControlled: {}
};
self.layerdescription = {
visited: '(Ever visited/total visible portals)',
capturedenl: '(Ever captured/total visible ENL portals)',
capturedres: '(Ever captured/total visible RES portals)',
capturedmac: '(Ever captured/total visible MAC portals)',
capturedneutral: '(Ever captured/total visible neutral portals)',
scoutControlled: '(Ever Scout Controlled/total visible portals)'
};
self.layerdescriptioninverted = {
visited: '(Never visited/total visible portals)',
capturedenl: '(Never captured/total visible ENL portals)',
capturedres: '(Never captured/total visible RES portals)',
capturedmac: '(Never captured/total visible MAC portals)',
capturedneutral: '(Never captured/total visible neutral portals)',
scoutControlled: '(Not Scout Controlled/total visible portals)'
};
self.colorpickeroptions = {
flat: false,
showInput: true,
showButtons: true,
showPalette: true,
showSelectionPalette: true,
allowEmpty: false,
hideAfterPaletteSelect: true,
palette: [
['#004000','#008000','#00C000'],
['#00FF00','#80FF80','#C0FFC0'],
['#000040','#000080','#0000C0','rgb(254, 254, 51)'],
['#4040FF','#8080FF','#C0C0FF','#0000FF'],
['#6A3400','#964A00','#C05F00','#00FFFF'],
['#E27000','#FF8309','#FFC287','#FF0000'],
['#a24ac3','#514ac3','#4aa8c3','#51c34a'],
['#c1c34a','#c38a4a','#c34a4a','#c34a6f'],
['#000000','#666666','#bbbbbb','#ffffff']
]};
self.requestlist = []; // [{guid:guid,cnt:cnt}]
self.requestmax = 3;
self.request = {};
self.requestlisttimer = 0;
self.requestlisttimeout = 0;
self.requestdelay = 100;
self.requestbookmarkfoldername = '';
self.requesttimerstarttotal = 0;
self.requesttimerstarttime = 0;
self.restoresettings = function() {
if (typeof localStorage[self.localstoragesettings] != 'string' || localStorage[self.localstoragesettings] == '') return;
try {
let settings = JSON.parse(localStorage[self.localstoragesettings]);
if (typeof settings === 'object' && settings instanceof Object && !(settings instanceof Array)) { // expect an object
for (const i in self.settings) {
if (i in settings && typeof settings[i] === typeof self.settings[i]) { // only accept settings from default template of same type
if (typeof self.settings[i] === 'object' && self.settings[i] instanceof Object && !(self.settings[i] instanceof Array)) { // 1 sublevel is supported
for (const a in self.settings[i]) {
if (a in settings[i] && typeof settings[i][a] === typeof self.settings[i][a]) {
self.settings[i][a] = settings[i][a];
}
}
} else {
self.settings[i] = settings[i];
}
}
}
}
} catch(e) {
return false;
}
};
self.storesettings = function() {
localStorage[self.localstoragesettings] = JSON.stringify(self.settings);
};
self.moveHistoryToNewPlugin = function() {
let localstoragehistory = `${self.pluginname}-history`;
if (typeof localStorage[localstoragehistory] == 'undefined' || !window.plugin.portalhistorysupport) return; // no old values found, or required plugin not found
if (typeof localStorage[localstoragehistory] == 'string' && localStorage[localstoragehistory] != '') {
try {
let storagehistoryraw = JSON.parse(localStorage[localstoragehistory]);
if (typeof storagehistoryraw == 'object' && storagehistoryraw instanceof Object && !(storagehistoryraw instanceof Array)) {
for (const guid in storagehistoryraw) { // convert history data to new plugin cache
if (storagehistoryraw[guid] > 0) window.plugin.portalhistorysupport.addcache(guid,storagehistoryraw[guid]);
}
}
delete localStorage[localstoragehistory]; // delete permanently
} catch(e) {
}
}
};
self.historySortString = function(history,layerkey,level) {
if (!history) return '';
let sortstring = (history[layerkey]?layerkey[0]:' ');
for (const layerkey in self.basiclayers) {
sortstring += (history[layerkey]?layerkey[0]:'z'); // descending
}
sortstring += level;
return sortstring;
};
self.setupPortalsList = function() {
if (!window.plugin.portalslist) return;
let colpos = 0;
for (colpos = 0; colpos < window.plugin.portalslist.fields.length; colpos++) { // find column Portal Name
if (window.plugin.portalslist.fields[colpos].title == 'Portal Name') {
break;
}
}
if (colpos >= window.plugin.portalslist.fields.length) colpos = 0; // default first colum if column name not found
// insert extra columns at colpos:
for (const layerkey in self.basiclayers) {
window.plugin.portalslist.fields.splice(colpos,0,{
title: layerkey[0], // first character
value: function(portal) { return (portal.options.data.history && portal.options.data.history[layerkey]); },
sortValue: function(value, portal) { return self.historySortString(portal.options.data.history,layerkey,portal.options.data.level); },
format: function(cell, portal, value) {
$(cell)
.append($('')
.html((portal.options.data.history && portal.options.data.history[layerkey]?layerkey[0]:(!portal.options.data.history || portal.options.data.history._raw == undefined?'?':'')))
.attr({
"class": "value",
"style": "font-family: courier",
}));
},
defaultOrder: -1 // descending
});
colpos++;
}
};
self.setupLayers = function() {
for (const layername in self.toggle_layers) {
// use separate layer togglers and actual drawing layers, to enable show/hide layers when zooming in/out:
self.toggle_layers[layername] = new window.L.LayerGroup();
window.layerChooser.addOverlay(self.toggle_layers[layername], (self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]), {default: true});
// window.addLayerGroup((self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]), self.toggle_layers[layername], true);
// create drawing layers:
self.layers[layername] = new window.L.FeatureGroup();
// setup initial drawing layers visibility:
if (self.layeractive(layername)) self.showlayer(layername);
}
// toggle drawing layers:
window.map.on('layeradd', function(obj) {
for (const layername in self.toggle_layers) {
if (obj.layer == self.toggle_layers[layername]) {
self.showlayer(layername);
}
}
if (window.layerChooser._layers[obj.layer._leaflet_id] && window.layerChooser._layers[obj.layer._leaflet_id].overlay) {
// if overlay layer is activated, then bring the layers to front, a moment/event later with a timeout:
window.setTimeout(self.layersbringToFront,0);
}
});
window.map.on('layerremove', function(obj) {
for (const layername in self.toggle_layers) {
if (obj.layer == self.toggle_layers[layername]) {
self.hidelayer(layername);
}
}
});
};
self.getMarkerStyle = function(portaloptions, layerkey, selected) {
let portalstyle = window.getMarkerStyleOptions(portaloptions);
let scaleRadius = window.portalMarkerScale();
if (selected) portalstyle.color = window.COLOR_SELECTED_PORTAL;
let layername = self.gettogglelayername(layerkey,portaloptions.team);
let markerformatting = self.markerformatting[layername];
let styleOptions = {
radius: markerformatting.radius,
stroke: markerformatting.stroke,
color: markerformatting.color,
weight: markerformatting.weight,
opacity: markerformatting.opacity,
dashArray: null,
fill: markerformatting.fill,
fillColor: markerformatting.fillColor,
fillOpacity: markerformatting.fillOpacity,
clickable: (layerkey == 'captured')
};
if (typeof styleOptions.radius == 'number') styleOptions.radius = styleOptions.radius * scaleRadius;
// convert values:
for (const id in styleOptions) {
switch (styleOptions[id]) {
case 'settingscolor':
styleOptions[id] = self.settings.color[layername];
break;
case 'portalcolor':
case 'portalradius':
case 'portalweight':
styleOptions[id] = portalstyle[id];
break;
}
}
return styleOptions;
};
self.createMarker = function(latlng, dataOptions, layerkey) {
// dataOptions = { guid: guid, data: data.portal.options }
let styleOptions = self.getMarkerStyle(dataOptions.data, layerkey, dataOptions.guid == window.selectedPortal);
let options = window.L.extend({}, dataOptions, styleOptions);
let marker = window.L.circleMarker(latlng, options);
marker.on('click', function() { window.renderPortalDetails(dataOptions.guid); });
marker.on('dblclick', function() { window.renderPortalDetails(dataOptions.guid); window.map.setView(latlng, 17); });
return marker;
};
self.gettogglelayername = function(layerkey,team) {
let layername;
if (layerkey == 'captured') {
switch (team) {
case window.TEAM_ENL:
layername = `${layerkey}enl`;
break;
case window.TEAM_RES:
layername = `${layerkey}res`;
break;
case (window.TEAM_MAC || 3):
layername = `${layerkey}mac`;
break;
case window.TEAM_NONE:
layername = `${layerkey}neutral`;
break;
default:
console.log('ERROR: gettogglelayername - unknown team',team);
}
} else if (layerkey in self.toggle_layers) {
layername = layerkey
} else {
console.log('ERROR: gettogglelayername - unknown layerkey',layerkey);
}
return layername;
};
self.zoomlevelhasportals = function() {
return window.getMapZoomTileParameters(window.getDataZoomForMapZoom(window.map.getZoom())).hasPortals;
};
self.onportalAdded = function(data) {
// data = {portal: marker, previousData: previousData}
if (!data.portal.options.data.history) return; // draw nothing if there is no history available (will never happen, but just in case)
if (!self.zoomlevelhasportals() && !self.settings.showwhenzoomedout) return;
let guid = data.portal.options.guid;
let latlng = data.portal.getLatLng();
let dataOptions = {
guid: guid,
data: data.portal.options
};
for (const layerkey in self.basiclayers) {
if (data.portal.options.data.history && data.portal.options.data.history._raw != undefined) {
if ((!self.settings.invertresults && data.portal.options.data.history[layerkey]) || (self.settings.invertresults && !data.portal.options.data.history[layerkey])) {
let marker = self.createMarker(latlng, dataOptions, layerkey);
let layername = self.gettogglelayername(layerkey,data.portal.options.team);
self.layers[layername].addLayer(marker);
self.layermarkers[layername][guid] = marker;
}
}
}
};
self.onportalRemoved = function(data) {
// data = {portal: p, data: p.options.data }
let guid = data.portal.options.guid;
for (const layername in self.layers) {
if (guid in self.layermarkers[layername]) {
self.layers[layername].removeLayer(self.layermarkers[layername][guid]);
delete self.layermarkers[layername][guid];
}
}
};
self.onzoomlevelschange = function() {
if (self.lastportalscale == window.portalMarkerScale()) return;
for (const layername in self.layers) {
let layerkey;
switch (layername) {
case 'visited':
case 'scoutControlled':
layerkey = layername;
break;
default:
layerkey = 'captured';
}
for (const guid in self.layermarkers[layername]) {
let styleOptions = self.getMarkerStyle(window.portals[guid].options,layerkey,guid === window.selectedPortal);
self.layermarkers[layername][guid].setStyle(styleOptions);
}
}
}
self.onportalSelected = function(data) {
let layerkey = 'captured'; // draw on captured layer
// restore portal color for unselected captured portal marker:
if (data.unselectedPortalGuid && (data.unselectedPortalGuid != data.selectedPortalGuid)) {
let styleOptions = self.getMarkerStyle(window.portals[data.unselectedPortalGuid].options,layerkey,false);
let layername = self.gettogglelayername(layerkey,window.portals[data.unselectedPortalGuid].options.team);
if (self.layermarkers[layername][data.unselectedPortalGuid]) self.layermarkers[layername][data.unselectedPortalGuid].setStyle(styleOptions);
}
// apply portal selected color to captured portal marker:
if (data.selectedPortalGuid) {
let styleOptions = self.getMarkerStyle(window.portals[data.selectedPortalGuid].options,layerkey,true);
let layername = self.gettogglelayername(layerkey,window.portals[data.selectedPortalGuid].options.team);
if (self.layermarkers[layername][data.selectedPortalGuid]) self.layermarkers[layername][data.selectedPortalGuid].setStyle(styleOptions);
}
};
self.layersbringToFront = function() {
for (const layername in self.layers) {
if (self.layeractive(layername) && self.layers[layername]._map) { // only if layer is visible
self.layers[layername].bringToFront();
}
}
};
self.showlayer = function(layername) {
// only show layer if map is at zoom level all portals
if (((!self.settings.showwhenzoomedout && self.zoomlevelhasportals()) || self.settings.showwhenzoomedout) && self.layers[layername] && !self.layers[layername]._map) window.map.addLayer(self.layers[layername]);
//self.layersbringToFront();
self.updatemenu();
};
self.hidelayer = function(layername) {
if (self.layers[layername] && self.layers[layername]._map) window.map.removeLayer(self.layers[layername]);
self.updatemenu();
};
self.displaytogglelayer = function(layername,display) {
if (display && !window.map.hasLayer(self.toggle_layers[layername])) {
window.map.addLayer(self.toggle_layers[layername]);
} else if (!display && window.map.hasLayer(self.toggle_layers[layername])) {
window.map.removeLayer(self.toggle_layers[layername]);
}
};
self.layeractive = function(findlayername) {
let overlayLayers = window.layerChooser.getLayers().overlayLayers;
for (let cnt = overlayLayers.length -1; cnt >= 0; cnt--) {
let layername = overlayLayers[cnt].name;
// compare with self.toggle_layernames and self.toggle_layernamesinverted
if (layername == self.toggle_layernames[findlayername] || layername == self.toggle_layernamesinverted[findlayername]) {
return overlayLayers[cnt].active;
}
}
return false;
};
self.updateMarkers = function(layername) {
let markerformatting = self.markerformatting[layername];
let styleOptions = {};
for (const id in markerformatting) {
if (markerformatting[id] == 'settingscolor') styleOptions[id] = self.settings.color[layername];
}
for (const guid in self.layermarkers[layername]) {
self.layermarkers[layername][guid].setStyle(styleOptions);
}
};
self.invertresults = function() {
// replace layer choosers with new names:
for (const layername in self.toggle_layernames) {
// copy current layer visibility status, to force same initial status when creating the new layer:
let oldlayername = (!self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]);
let enabled = window.map.hasLayer(self.toggle_layers[layername]); // window.isLayerGroupDisplayed(oldlayername);
let newlayername = (self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]);
if (typeof window.layerChooser?._isOverlayDisplayed == 'function') { // IITC 0.32.1 Beta and up
if (window.layerChooser._isOverlayDisplayed(newlayername,false) != enabled) {
window.layerChooser._storeOverlayState(newlayername,enabled); // force start status
}
window.layerChooser.removeLayer(self.toggle_layers[layername]);
self.toggle_layers[layername] = new window.L.LayerGroup();
window.layerChooser.addOverlay(self.toggle_layers[layername], newlayername, {default: enabled});
} else if (typeof window.isLayerGroupDisplayed == 'function') { // IITC 0.32.1 Release and before
if (window.isLayerGroupDisplayed(newlayername,false) != enabled) {
window.updateDisplayedLayerGroup(newlayername,enabled); // force start status
}
window.removeLayerGroup(self.toggle_layers[layername]);
self.toggle_layers[layername] = new window.L.LayerGroup();
window.addLayerGroup(newlayername, self.toggle_layers[layername], enabled);
}
// remove all markers:
self.layers[layername].clearLayers();
self.layermarkers[layername] = {};
}
// draw all new markers:
for (const guid in window.portals) {
let data = {portal: window.portals[guid], previousData: undefined};
self.onportalAdded(data);
}
};
self.onzoomend = function() {
let ZOOM_LEVEL_ALL_PORTALS = self.zoomlevelhasportals();
for (const layername in self.layers) {
if (!ZOOM_LEVEL_ALL_PORTALS && !self.settings.showwhenzoomedout) { // hide layers
window.map.removeLayer(self.layers[layername]);
} else if (self.layeractive(layername)) { // show if enabled
window.map.addLayer(self.layers[layername]);
}
}
};
self.about = function() {
let container = document.createElement('div');
container.innerHTML = `
Thank you for choosing this plugin. You can visualize your unique history with 6 toggle layers: You can toggle these 6 layers individually from the menu or from the layer selector. The Portals List plugin (if enabled) will show extra columns for visit, capture and scout controlled (v c s columns) and can be sorted. You can draw (and remove) Bookmarks (if plugin is enabled) for groups of portals which are never visited or captured. Bookmarks are automatically created in named folders. With the Bookmarks add-on you can draw colored bookmarks.
`;
container.querySelector('a[name=portalhistorysupport]').addEventListener('click',function(e) {
e.preventDefault();
self.missingPortalHistorySupportplugin();
},false);
window.dialog({
html: container,
id: `${self.pluginname}-dialog`,
width: 'auto',
title: `${self.title} - About`
}).dialog('option', 'buttons', {
'< Main menu': function() { self.menu(); },
'Changelog': function() { alert(self.changelog); },
'Close': function() { $(this).dialog('close'); },
});
};
self.showAllBookmarks = function() {
let bookmarklayers = window.plugin.bookmarks.starLayerGroup.getLayers();
if (bookmarklayers.length === 0) {
alert('There are no bookmarks');
return;
}
// The bookmarks starLayerGroup is of type L.LayerGroup
// The L.LayerGroup does not support getBounds
// Convert all bookmarks to simple markers on a temporary layer of type L.FeatureGroup
let layer = new window.L.FeatureGroup();
for (let bookmarklayer of bookmarklayers) {
let latlng = bookmarklayer.getLatLng();
window.L.marker([latlng.lat, latlng.lng]).addTo(layer);
}
window.map.fitBounds(layer.getBounds());
};
self.drawbookmarks = function(bookmarkslist,replaceexisting) { // {
You can also choose your own colors for every layer.
You can invert the results to Never. The layer selector names will change accordingly to Never Visited, Never Captured (ENL, RES, MAC and Neutral) and Not Scout Controlled.
When zooming out to "link" levels, the layers will be hidden, unless Show markers when zooming out is enabled.
If the plugin Portal History Support is enabled, it is also possible to draw bookmarks for portals anywhere for cached history portals.
Loading details for ${self.requesttimerstarttotal} portals.
To do: ${self.requestlist.length}
Estimated time left: ?
Please be patient...
`;
}
container.innerHTML += `
Zoom to see all bookmarks
`;
if (menu == 'main') {
container.innerHTML += `
Draw bookmarks on visible portals:
`;
if (window.plugin.portalhistorysupport) {
container.innerHTML += `
Portal History Bookmarks menu...
`;
}
container.innerHTML += `
Remove bookmarks menu...
`;
for (let layerkey in self.bookmarkfoldernames) {
let bookmarkfoldername = (layerkey.match('scoutControlled')?'Not':'Never') + ' ' + self.bookmarkfoldernames[layerkey];
let a = container.querySelector('div.visibleportals').appendChild(document.createElement('a'));
a.className = 'dialogbutton';
a.innerHTML = `${bookmarkfoldername} (/)`;
a.addEventListener('click',function(e) {
self.addbookmarksvisible(layerkey,bookmarkfoldername,true);
e.preventDefault();
},false);
}
} else if (menu == 'cached') {
container.innerHTML += `
Draw bookmarks for cached portals (slow):
Visited ()
Captured ()
Visited but never Captured ()
Scout Controlled ()
Visible portals menu...
Remove bookmarks menu...
`;
} else if (menu == 'remove') {
container.innerHTML += `
Remove bookmarks for visible portals:
Remove from Visited portals ()
Remove from Captured portals ()
Remove all visible bookmarks ()
Remove ALL bookmarks ()
Visible portals menu...
`;
if (window.plugin.portalhistorysupport) {
container.innerHTML += `
Portal History Bookmarks menu...
`;
}
}
container.querySelector(`input[type=checkbox][name=replacebookmarks]`).checked = self.settings.replacebookmarks;
container.querySelector(`input[type=checkbox][name=replacebookmarks]`).addEventListener('click',function(e) {
self.settings.replacebookmarks = this.checked;
self.storesettings();
},false);
if (window.plugin.bookmarksAddon) {
$(container.querySelector(`input[type=color]`)).spectrum({
...self.colorpickeroptions,
change: function(color) {
self.settings.bookmarkscolor = color.toHexString();
self.storesettings();
},
color: self.settings.bookmarkscolor,
});
}
self.updatemenu(container);
}
let bookmarksbutton = (window.plugin.bookmarks? { 'Bookmarks plugin': function() { window.plugin.bookmarks.manualOpt(); } } : {});
window.dialog({
html: container,
id: `${self.pluginname}-dialog`,
title: `${self.title} - Bookmarks`,
width: 'auto'
}).dialog('option', 'buttons', {
'< Main menu': function() { self.menu(); },
...bookmarksbutton,
'Close': function() { $(this).dialog('close'); },
});
};
self.getBookmarkPortalGuids = function() {
let guidlist = {};
if (!window.plugin.bookmarks) return guidlist;
let visiblebounds = window.map.getBounds();
for (const ID in window.plugin.bookmarks.bkmrksObj.portals) {
for (const bkmrkid in window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk) {
let bookmark = window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk[bkmrkid];
let latlng = JSON.parse('[' + bookmark.latlng + ']');
guidlist[bookmark.guid] = {
...bookmark,
folder: window.plugin.bookmarks.bkmrksObj.portals[ID].label,
visible: visiblebounds.contains(latlng)
}
}
}
return guidlist;
};
self.count = function() {
let bookmarkportalguids = self.getBookmarkPortalGuids();
let displayBounds = window.map.getBounds();
let count = {
total: 0,
history: 0,
nevervisited: 0,
nevervisitedCached: 0,
visited: 0,
visitedCached: 0,
visitednevercapturedCached: 0,
nevercaptured: 0,
nevercapturedCached: 0,
captured: 0,
capturedCached: 0,
neverscoutControlled: 0,
neverscoutControlledCached: 0,
scoutControlled: 0,
scoutControlledCached: 0,
enl: 0,
nevercapturedenl: 0,
capturedenl: 0,
res: 0,
nevercapturedres: 0,
capturedres: 0,
mac: 0,
nevercapturedmac: 0,
capturedmac: 0,
neutral: 0,
nevercapturedneutral: 0,
capturedneutral: 0,
bookmarks: 0,
bookmarksvisible: 0,
bookmarksalreadyvisited: 0,
bookmarksalreadycaptured: 0
};
count.bookmarks = Object.keys(bookmarkportalguids).length;
for (const guid in bookmarkportalguids) {
if (bookmarkportalguids[guid].visible) count.bookmarksvisible++;
}
for (const guid in window.portals) {
let portal = window.portals[guid];
if (displayBounds.contains(portal.getLatLng())) {
count.total++;
if (portal.options.data.history && portal.options.data.history._raw != undefined) {
count.history++;
if (portal.options.data.history.visited) count.visited++; else count.nevervisited++;
if (portal.options.data.history.captured) count.captured++; else count.nevercaptured++;
if (portal.options.data.history.scoutControlled) count.scoutControlled++; else count.neverscoutControlled++;
if (portal.options.team == window.TEAM_ENL) {
count.enl++;
if (portal.options.data.history.captured) count.capturedenl++; else count.nevercapturedenl++;
}
if (portal.options.team == window.TEAM_RES) {
count.res++;
if (portal.options.data.history.captured) count.capturedres++; else count.nevercapturedres++;
}
if (portal.options.team == (window.TEAM_MAC || 3)) {
count.mac++;
if (portal.options.data.history.captured) count.capturedmac++; else count.nevercapturedmac++;
}
if (portal.options.team == window.TEAM_NONE) {
count.neutral++;
if (portal.options.data.history.captured) count.capturedneutral++; else count.nevercapturedneutral++;
}
if (bookmarkportalguids[guid]?.visible) { // bookmark for portal exists and is visible
if (portal.options.data.history.visited) count.bookmarksalreadyvisited++;
if (portal.options.data.history.captured) count.bookmarksalreadycaptured++;
}
}
}
}
if (window.plugin.portalhistorysupport) {
for (const guid in window.plugin.portalhistorysupport.cache[window.PLAYER.nickname]) {
let bitarray = window.plugin.portalhistorysupport.cache[window.PLAYER.nickname][guid];
let history = window.plugin.portalhistorysupport.decodeHistory(bitarray);
if (history.visited) count.visitedCached++; else count.nevervisitedCached++;
if (history.captured) count.capturedCached++; else count.nevercapturedCached++;
if (history.visited && !history.captured) count.visitednevercapturedCached++;
if (history.scoutControlled) count.scoutControlledCached++; else count.neverscoutControlledCached++;
}
}
return count;
};
self.updatemenu = function(container) {
if (!(container instanceof HTMLElement)) container = window.DIALOGS[`dialog-${self.pluginname}-dialog`];
if (!container) return;
let count = self.count();
for (let id in count) {
container.querySelectorAll(`span[name=count_${id}]`).forEach((el)=>{
el.innerHTML = count[id];
});
}
if (container.querySelector('div[name=layerrows]')) {
for (const layername in self.toggle_layers) {
let row = container.querySelector(`div[name=${layername}-row]`);
row.querySelector('span[name=layer]').innerHTML = (self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]);
row.querySelector('input[type=checkbox]').checked = self.layeractive(layername);
row.querySelector('span[name=count]').innerHTML = (self.settings.invertresults?count[`never${layername}`]:count[layername]) + '/' + (layername.match(/captured/) ? count[layername.replace('captured','')] : count.total);
row.querySelector('span[name=count]').setAttribute('title',(self.settings.invertresults?self.layerdescriptioninverted[layername]:self.layerdescription[layername]));
}
}
};
self.menu = function() {
let container = document.createElement('div');
container.innerHTML = `
Visible portals with history:
Total visible portals:
Show/hide layers:
`;
for (const layername in self.toggle_layers) {
let row = container.querySelector('div[name=layerrows]').appendChild(document.createElement('div'));
row.setAttribute('name',`${layername}-row`);
row.innerHTML = ``;
row.querySelector('input[type=checkbox]').addEventListener('click',function(e) {
self.displaytogglelayer(layername,this.checked);
},false);
$(row.querySelector('input[type=color]')).spectrum({
...self.colorpickeroptions,
change: function(color) {
self.settings.color[layername] = color.toHexString();
self.storesettings();
self.updateMarkers(layername);
},
color: self.settings.color[layername]
});
}
container.querySelector('input[name=showwhenzoomedout]').checked = self.settings.showwhenzoomedout;
container.querySelector('input[name=showwhenzoomedout]').addEventListener('click',function(e) {
self.settings.showwhenzoomedout = this.checked;
self.onzoomend();
self.storesettings();
},false);
container.querySelector('input[name=invertresults]').checked = self.settings.invertresults;
container.querySelector('input[name=invertresults]').addEventListener('click',function(e) {
self.settings.invertresults = this.checked;
self.invertresults();
self.storesettings();
self.updatemenu(container);
},false);
self.updatemenu(container);
if (window.useAndroidPanes()) window.show('map'); // hide sidepane
window.dialog({
html: container,
id: `${self.pluginname}-dialog`,
title: self.title,
width: 'auto'
}).dialog('option', 'buttons', {
'Bookmarks': function() { self.bookmarks(); },
'About': function() { self.about(); },
'Ok': function() { $(this).dialog('close'); },
});
};
self.setupColorpickerSpectrum = function() {
// source: https://github.com/bgrins/spectrum
// minified with https://www.minifier.org/
// Spectrum Colorpicker v1.8.1
// https://github.com/bgrins/spectrum
// Author: Brian Grinstead
// License: MIT
(function(factory){"use strict";if(typeof define==='function'&&define.amd){define(['jquery'],factory)}else if(typeof exports=="object"&&typeof module=="object"){module.exports=factory(require('jquery'))}else{factory(jQuery)}})(function($,undefined){"use strict";var defaultOpts={beforeShow:noop,move:noop,change:noop,show:noop,hide:noop,color:!1,flat:!1,showInput:!1,allowEmpty:!1,showButtons:!0,clickoutFiresChange:!0,showInitial:!1,showPalette:!1,showPaletteOnly:!1,hideAfterPaletteSelect:!1,togglePaletteOnly:!1,showSelectionPalette:!0,localStorageKey:!1,appendTo:"body",maxSelectionSize:7,cancelText:"cancel",chooseText:"choose",togglePaletteMoreText:"more",togglePaletteLessText:"less",clearText:"Clear Color Selection",noColorSelectedText:"No Color Selected",preferredFormat:!1,className:"",containerClassName:"",replacerClassName:"",showAlpha:!1,theme:"sp-light",palette:[["#ffffff","#000000","#ff0000","#ff8000","#ffff00","#008000","#0000ff","#4b0082","#9400d3"]],selectionPalette:[],disabled:!1,offset:null},spectrums=[],IE=!!/msie/i.exec(window.navigator.userAgent),rgbaSupport=(function(){function contains(str,substr){return!!~(''+str).indexOf(substr)}
var elem=document.createElement('div');var style=elem.style;style.cssText='background-color:rgba(0,0,0,.5)';return contains(style.backgroundColor,'rgba')||contains(style.backgroundColor,'hsla')})(),replaceInput=["
Thank you for choosing this plugin.
The plugin was recently renamed from "Portal Agent Status" to "${self.title}".
`; if (window.plugin.portalagentstatus) { container.innerHTML += `You now have both plugins activated and this can cause a conflict.
Before you can use this new plugin, you must manually disable and/or remove the old plugin "Portal Agent Status".
The new plugin will not run until this is changed.
`; disableplugin = true; } else if (localStorage[oldlocalstoragesettings]) { container.innerHTML += `You need to check the new plugin settings because the old settings are not compatible and have been deleted.
Main menu
About
There have been a lot of changes, so make sure you check out the About menu.
This plugin can make use of another plugin: Portal History Support
You can download it here: softspot.nl
You will get more bookmark features when it is installed:
You can draw bookmarks on cached portal with history, without the need to load them or have them within viewing range.