Calculates the link range for portals with <8 resonators as well, but uses a dashed circle and a striked-through range value for those. The tooltip over the range value in the details pane shows base range, link amp boost and total range. The range for portals with missing resonators can be important because links with shorter lengths might originate here. Those will collapse when the total link range drops below their length, but not as soon as a resonator is destroyed.
192 lines
7.9 KiB
JavaScript
192 lines
7.9 KiB
JavaScript
// PORTAL DETAILS MAIN ///////////////////////////////////////////////
|
||
// main code block that renders the portal details in the sidebar and
|
||
// methods that highlight the portal in the map view.
|
||
|
||
window.renderPortalDetails = function(guid) {
|
||
selectPortal(window.portals[guid] ? guid : null);
|
||
|
||
if(!window.portals[guid]) {
|
||
urlPortal = guid;
|
||
$('#portaldetails').html('');
|
||
if(isSmartphone()) {
|
||
$('.fullimg').remove();
|
||
$('#mobileinfo').html('');
|
||
}
|
||
return;
|
||
}
|
||
|
||
var d = window.portals[guid].options.details;
|
||
|
||
// collect some random data that’s not worth to put in an own method
|
||
var links = {incoming: 0, outgoing: 0};
|
||
if(d.portalV2.linkedEdges) $.each(d.portalV2.linkedEdges, function(ind, link) {
|
||
links[link.isOrigin ? 'outgoing' : 'incoming']++;
|
||
});
|
||
function linkExpl(t) { return '<tt title="↳ incoming links\n↴ outgoing links\n• is the portal">'+t+'</tt>'; }
|
||
var linksText = [linkExpl('links'), linkExpl(' ↳ ' + links.incoming+' • '+links.outgoing+' ↴')];
|
||
|
||
var player = d.captured && d.captured.capturingPlayerId
|
||
? '<span class="nickname">' + getPlayerName(d.captured.capturingPlayerId) + '</span>'
|
||
: null;
|
||
var playerText = player ? ['owner', player] : null;
|
||
|
||
var time = d.captured
|
||
? '<span title="' + unixTimeToDateTimeString(d.captured.capturedTime, false) + '">'
|
||
+ unixTimeToString(d.captured.capturedTime) + '</span>'
|
||
: null;
|
||
var sinceText = time ? ['since', time] : null;
|
||
|
||
var linkedFields = ['fields', d.portalV2.linkedFields ? d.portalV2.linkedFields.length : 0];
|
||
|
||
// collect and html-ify random data
|
||
var randDetails = [
|
||
playerText, sinceText,
|
||
getRangeText(d), getEnergyText(d),
|
||
linksText, getAvgResoDistText(d),
|
||
linkedFields, getAttackApGainText(d),
|
||
getHackDetailsText(d), getMitigationText(d)
|
||
];
|
||
randDetails = '<table id="randdetails">' + genFourColumnTable(randDetails) + '</table>';
|
||
|
||
var resoDetails = '<table id="resodetails">' + getResonatorDetails(d) + '</table>';
|
||
|
||
var img = getPortalImageUrl(d);
|
||
var lat = d.locationE6.latE6/1E6;
|
||
var lng = d.locationE6.lngE6/1E6;
|
||
var perma = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;
|
||
var imgTitle = 'title="'+getPortalDescriptionFromDetails(d)+'\n\nClick to show full image."';
|
||
var poslinks = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(d.portalV2.descriptiveText.TITLE)+'\')';
|
||
var portalDetailObj = window.getPortalDescriptionFromDetailsExtended(d);
|
||
|
||
var portalDetailedDescription = '';
|
||
|
||
if(portalDetailObj) {
|
||
portalDetailedDescription = '<table description="Portal Photo Details" class="portal_details">';
|
||
|
||
// TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them
|
||
// (at this time the data isn't returned from the server - although a count of images IS returned!)
|
||
|
||
if(portalDetailObj.submitter.name.length > 0) {
|
||
if(portalDetailObj.submitter.team) {
|
||
submitterSpan = '<span class="' + (portalDetailObj.submitter.team === 'RESISTANCE' ? 'res' : 'enl') + ' nickname">';
|
||
} else {
|
||
submitterSpan = '<span class="none">';
|
||
}
|
||
portalDetailedDescription += '<tr><th>Photo by:</th><td>' + submitterSpan
|
||
+ escapeHtmlSpecialChars(portalDetailObj.submitter.name) + '</span>'+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'</td></tr>';
|
||
}
|
||
if(portalDetailObj.submitter.link.length > 0) {
|
||
portalDetailedDescription += '<tr><th>Photo from:</th><td><a href="'
|
||
+ escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '">' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '</a></td></tr>';
|
||
}
|
||
|
||
if(portalDetailObj.description) {
|
||
portalDetailedDescription += '<tr class="padding-top"><th>Description:</th><td>' + escapeHtmlSpecialChars(portalDetailObj.description) + '</td></tr>';
|
||
}
|
||
// if(d.portalV2.descriptiveText.ADDRESS) {
|
||
// portalDetailedDescription += '<tr><th>Address:</th><td>' + escapeHtmlSpecialChars(d.portalV2.descriptiveText.ADDRESS) + '</td></tr>';
|
||
// }
|
||
|
||
portalDetailedDescription += '</table>';
|
||
}
|
||
|
||
var levelDetails = getPortalLevel(d);
|
||
if(levelDetails != 8) {
|
||
if(levelDetails==Math.ceil(levelDetails))
|
||
levelDetails += "\n8";
|
||
else
|
||
levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
|
||
levelDetails += " resonator level(s) needed for next portal level";
|
||
} else {
|
||
levelDetails += "\nfully upgraded";
|
||
}
|
||
levelDetails = "Level " + levelDetails;
|
||
|
||
$('#portaldetails')
|
||
.attr('class', TEAM_TO_CSS[getTeam(d)])
|
||
.html(''
|
||
+ '<h3 class="title">'+escapeHtmlSpecialChars(d.portalV2.descriptiveText.TITLE)+'</h3>'
|
||
+ '<span class="close" onclick="renderPortalDetails(null); if(isSmartphone()) show(\'map\');" title="Close">X</span>'
|
||
// help cursor via ".imgpreview img"
|
||
+ '<div class="imgpreview" '+imgTitle+' style="background-image: url('+img+')">'
|
||
+ '<span id="level" title="'+levelDetails+'">'+Math.floor(getPortalLevel(d))+'</span>'
|
||
+ '<div class="portalDetails">'+ portalDetailedDescription + '</div>'
|
||
+ '<img class="hide" src="'+img+'"/></div>'
|
||
+ '</div>'
|
||
+ '<div class="mods">'+getModDetails(d)+'</div>'
|
||
+ randDetails
|
||
+ resoDetails
|
||
+ '<div class="linkdetails">'
|
||
+ (
|
||
typeof android !== 'undefined' && android && android.intentPosLink // Android handles both links via a dialog
|
||
? '<aside><a onclick="'+poslinks+'" title="Create a URL link to this portal" >Portal link</a></aside>'
|
||
: '<aside><a href="'+perma+'" onclick="return androidCopy(this.href)" title="Create a URL link to this portal" >Portal link</a></aside>'
|
||
+ '<aside><a onclick="'+poslinks+'" title="Link to alternative maps (Google, etc)">Map links</a></aside>'
|
||
)
|
||
+ '</div>'
|
||
);
|
||
|
||
// try to resolve names that were required for above functions, but
|
||
// weren't available yet.
|
||
resolvePlayerNames();
|
||
|
||
runHooks('portalDetailsUpdated', {portalDetails: d});
|
||
}
|
||
|
||
// draws link-range and hack-range circles around the portal with the
|
||
// given details. Clear them if parameter 'd' is null.
|
||
window.setPortalIndicators = function(d) {
|
||
if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
|
||
portalRangeIndicator = null;
|
||
if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
|
||
portalAccessIndicator = null;
|
||
|
||
if(d === null) return;
|
||
|
||
var range = getPortalRange(d);
|
||
var coord = [d.locationE6.latE6/1E6, d.locationE6.lngE6/1E6];
|
||
portalRangeIndicator = (range.range > 0
|
||
? L.geodesicCircle(coord, range.range, {
|
||
fill: false,
|
||
color: RANGE_INDICATOR_COLOR,
|
||
weight: 3,
|
||
dashArray: range.isLinkable ? undefined : "10,10",
|
||
clickable: false })
|
||
: L.circle(coord, range, { fill: false, stroke: false, clickable: false })
|
||
).addTo(map);
|
||
|
||
portalAccessIndicator = L.circle(coord, HACK_RANGE,
|
||
{ fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
|
||
).addTo(map);
|
||
}
|
||
|
||
// highlights portal with given GUID. Automatically clears highlights
|
||
// on old selection. Returns false if the selected portal changed.
|
||
// Returns true if it's still the same portal that just needs an
|
||
// update.
|
||
window.selectPortal = function(guid) {
|
||
var update = selectedPortal === guid;
|
||
var oldPortalGuid = selectedPortal;
|
||
selectedPortal = guid;
|
||
|
||
var oldPortal = portals[oldPortalGuid];
|
||
var newPortal = portals[guid];
|
||
|
||
// Restore style of unselected portal
|
||
if(!update && oldPortal) setMarkerStyle(oldPortal,false);
|
||
|
||
// Change style of selected portal
|
||
if(newPortal) {
|
||
setMarkerStyle(newPortal, true);
|
||
|
||
if (map.hasLayer(newPortal)) {
|
||
newPortal.bringToFront();
|
||
}
|
||
}
|
||
|
||
setPortalIndicators(newPortal ? newPortal.options.details : null);
|
||
|
||
runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
|
||
return update;
|
||
}
|