// ==UserScript==
// @author DanielOnDiordna
// @name Unique Portal History
// @category Layer
// @version 2.1.0.20220711.235400
// @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.1.0.20220711.235400] Show your personal unique portal history for Visited, Captured or Scout Controlled portals with layers. Choose your own colors. Place bookmarks. Invert results! Add three extra Portals List plugin columns. Requires 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.1.0.20220711.235400';
self.author = 'DanielOnDiordna';
self.changelog = `
Changelog:
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',
capturedneutral: '#000000',
scoutControlled: 'yellow'
};
self.settings.replacebookmarks = true;
self.settings.bookmarkscolor = 'yellow';
self.settings.showwhenzoomedout = false;
self.capturedlayers = {
capturedenl: window.TEAM_ENL,
capturedres: window.TEAM_RES,
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' },
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.toggle_layernames = {
visited: 'Visited',
capturedenl: 'Captured ENL',
capturedres: 'Captured RES',
capturedneutral: 'Captured neutral',
scoutControlled: 'Scout Controlled'
};
self.toggle_layernamesinverted = {
visited: 'Never Visited',
capturedenl: 'Never Captured ENL',
capturedres: 'Never Captured RES',
capturedneutral: 'Never Captured neutral',
scoutControlled: 'Not Scout Controlled'
};
self.toggle_layers = {
visited: undefined,
capturedenl: undefined,
capturedres: undefined,
capturedneutral: undefined,
scoutControlled: undefined,
};
self.layers = {
visited: undefined,
capturedenl: undefined,
capturedres: undefined,
capturedneutral: undefined,
scoutControlled: undefined
};
self.layermarkers = {
visited: {},
capturedenl: {},
capturedres: {},
capturedneutral: {},
scoutControlled: {}
};
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 = [];
self.requestguid = undefined;
self.requestlisttimer = 0;
self.requestlisttimeout = 0;
self.requestdelay = 100;
self.requestbookmarkfoldername = '';
self.restoresettings = function() {
if (typeof localStorage[self.localstoragesettings] != 'string' || localStorage[self.localstoragesettings] == '') return;
try {
var 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 {
var 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 L.LayerGroup();
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 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];
var 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);
var options = L.extend({}, dataOptions, styleOptions);
var marker = 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_NONE:
layername = layerkey + 'neutral';
break;
}
} else if (layerkey in self.toggle_layers) {
layername = 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) {
//console.log('onportalAdded',layerkey,data.portal.options.data.history);
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;
console.log('onzoomlevelschange: resize markers');
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
// console.log('layersbringToFront',layername,self.layers[layername]);
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 && !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) {
var 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]) {
//console.log('layeractive',findlayername,layername,layers[cnt].active);
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.isLayerGroupDisplayed(oldlayername);
let newlayername = (self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]);
if (window.isLayerGroupDisplayed(newlayername) != enabled) {
if (typeof window.updateDisplayedLayerGroup == "function") { // IITC 0.32.1 Release
window.updateDisplayedLayerGroup(newlayername,enabled); // force start status
} else if (typeof window.layerChooser._storeOverlayState == "function") { // IITC 0.32.1 Beta
window.layerChooser._storeOverlayState(newlayername,enabled); // force start status
}
}
window.removeLayerGroup(self.toggle_layers[layername]);
window.addLayerGroup((self.settings.invertresults?self.toggle_layernamesinverted[layername]:self.toggle_layernames[layername]), 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 html = '
' +
'Thank you for choosing this plugin. ' +
' ' +
'You can visualize your unique history with 5 toggle layers: ' +
'- Visited: draw a small circle around the portal. ' +
'- Captured: draw a filled portal, with a separate layer for ENL, RES and Neutral portals. ' +
'- Scout Controlled: a larger circle around the portal. ' +
' ' +
'You can toggle these 5 layers individually from the menu or on the layer selector. ' +
'You can also choose your own colors for every layer. ' +
'You can invert the results. The layer selector names will change accordingly to Never Visited, Never Captured (ENL, RES 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. ' +
' ' +
'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. ' +
'Also an option to add bookmarks for portals anywhere for cached history portals (if plugin portalhistorysupport is installed). ' +
'' + self.title + ' version ' + self.version + ' by ' + self.author + '' +
'
';
window.dialog({
html: html,
id: self.pluginname + '-dialog',
dialogClass: 'ui-dialog-' + self.pluginname,
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.clearbookmarktimerlist = function() {
for (let cnt = self.bookmarktimerlist.length - 1; cnt >= 0; cnt--) {
window.clearTimeout(self.bookmarktimerlist[cnt]);
}
self.bookmarktimerlist = [];
self.requestlist = [];
clearTimeout(self.requestlisttimer);
self.requestlisttimer = 0;
clearTimeout(self.requestlisttimeout);
self.requestlisttimeout = 0;
self.requestbookmarkfoldername = '';
if (self.bookmarkrestorecolor) window.plugin.bookmarksAddon.settings.color = self.bookmarkrestorecolor;
self.bookmarkrestorecolor = undefined;
$('#dialog-' + self.pluginname + '-dialog-bookmarks').dialog('close');
};
self.createNewBookmarkFolder = function(foldername) {
for (const ID in window.plugin.bookmarks.bkmrksObj.portals) {
if (window.plugin.bookmarks.bkmrksObj.portals[ID].label == foldername) // folder already exists
return ID;
}
let ID = window.plugin.bookmarks.generateID();
window.plugin.bookmarks.bkmrksObj.portals[ID] = {"label":foldername,"state":1,"bkmrk":{}};
window.plugin.bookmarks.saveStorage();
window.plugin.bookmarks.refreshBkmrks();
window.runHooks('pluginBkmrksEdit', {"target": 'portals', "action": "add", "id": ID});
return ID;
};
self.createOrMoveBookMarkInFolder = function(portalguid,foldername) {
if (!(portalguid in window.portals) || !window.portals[portalguid].options.data.title) {
console.log("portal details missing",portalguid);
return;
}
if (!foldername) foldername = "Others"; // default
// find existing bookmark and folder ids:
let bookmarkid, oldfolderid, newfolderid;
for (const ID in window.plugin.bookmarks.bkmrksObj.portals) {
if (window.plugin.bookmarks.bkmrksObj.portals[ID].label == foldername)
newfolderid = ID;
for (const bkmrkID in window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk) {
if (window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk[bkmrkID].guid == portalguid) {
oldfolderid = ID;
bookmarkid = bkmrkID;
}
}
}
if (!newfolderid) newfolderid = self.createNewBookmarkFolder(foldername); // create new folder
if (!bookmarkid) { // create new bookmark
let portal = window.portals[portalguid];
let latlng = portal.getLatLng();
let label = portal.options.data.title;
window.plugin.bookmarks.addPortalBookmark(portalguid,latlng.lat + ',' + latlng.lng,label); // default added to idOthers
let ID = window.plugin.bookmarks.KEY_OTHER_BKMRK;
for (const bkmrkID in window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk) {
if (window.plugin.bookmarks.bkmrksObj.portals[ID].bkmrk[bkmrkID].guid == portalguid) {
oldfolderid = ID;
bookmarkid = bkmrkID;
}
}
}
if (newfolderid != oldfolderid) { // move when needed
var Bkmrk = window.plugin.bookmarks.bkmrksObj.portals[oldfolderid].bkmrk[bookmarkid];
delete window.plugin.bookmarks.bkmrksObj.portals[oldfolderid].bkmrk[bookmarkid];
window.plugin.bookmarks.bkmrksObj.portals[newfolderid].bkmrk[bookmarkid] = Bkmrk;
window.plugin.bookmarks.saveStorage();
window.plugin.bookmarks.refreshBkmrks();
window.runHooks('pluginBkmrksEdit', {"target": "bookmarks", "action": "sort"});
}
};
self.addbookmarkanywhererequestnext = function() {
self.requestguid = self.requestlist.shift();
$('#' + self.id + '_count_addbookmarks').html(parseInt($('#' + self.id + '_count_addbookmarks').text()) + 1);
self.requestlisttimer = setTimeout(function() {
let guid = self.requestguid;
self.requestlisttimeout = setTimeout(function() {
// retry
self.requestlisttimeout = 0;
console.log("request timeout",guid);
self.requestlist.push(guid);
self.addbookmarkanywhererequestnext();
},1000); // timeout
window.portalDetail.request(guid);
},self.requestdelay);
};
self.addbookmarkanywhererequestloaded = function(data) {
let guid = data.guid;
if (self.requestguid != guid) return;
self.requestguid = undefined;
clearTimeout(self.requestlisttimeout);
self.requestlisttimeout = 0;
if (data.success) {
self.createOrMoveBookMarkInFolder(guid,self.requestbookmarkfoldername);
} else {
// retry
console.log("load portal details failure",data);
self.requestlist.push(guid);
}
if (self.requestlist.length == 0) {
console.log("all requests finished");
self.clearbookmarktimerlist();
} else {
self.addbookmarkanywhererequestnext();
}
};
self.addbookmarksanywherecached = function(bookmarkgroup,never) {
if (!window.plugin.portalhistorysupport) return;
if (Object.keys(self.requestlist).length > 0) return; // busy
let bookmarkportalguids = self.getBookmarkPortalGuids();
let addbookmarks = [];
let addbookmarkrequests = [];
let bookmarkfoldername = (never?'Never ':'') + bookmarkgroup;
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 ((bookmarkgroup == "Visited but never Captured" && history.visited && !history.captured) || (!never && history[bookmarkgroup]) || (never && !history[bookmarkgroup])) {
if (guid in bookmarkportalguids && self.settings.replacebookmarks) { // bookmark for portal exists and is visible
window.plugin.bookmarks.switchStarPortal(guid); // remove
addbookmarks.push(guid);
} else if (!(guid in bookmarkportalguids)) { // no bookmark for portal exists
if (guid in window.portals && window.portals[guid].options.data.title) { // portal already loaded
addbookmarks.push(guid);
} else {
addbookmarkrequests.push(guid);
}
}
}
}
self.clearbookmarktimerlist();
if ((addbookmarks.length + addbookmarkrequests.length) > 20) // only show a dialog when drawing bookmarks takes a long time
window.dialog({
html: '