277 lines
9.4 KiB
JavaScript
277 lines
9.4 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 (guid && !portalDetail.isFresh(guid)) {
|
||
portalDetail.request(guid);
|
||
}
|
||
|
||
// TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
|
||
// window.portals....
|
||
|
||
if(!window.portals[guid]) {
|
||
urlPortal = guid;
|
||
$('#portaldetails').html('');
|
||
if(isSmartphone()) {
|
||
$('.fullimg').remove();
|
||
$('#mobileinfo').html('<div style="text-align: center"><b>tap here for info screen</b></div>');
|
||
}
|
||
return;
|
||
}
|
||
|
||
var portal = window.portals[guid];
|
||
var data = portal.options.data;
|
||
var details = portalDetail.get(guid);
|
||
|
||
// details and data can get out of sync. if we have details, construct a matching 'data'
|
||
if (details) {
|
||
data = getPortalSummaryData(details);
|
||
}
|
||
|
||
|
||
var modDetails = details ? '<div class="mods">'+getModDetails(details)+'</div>' : '';
|
||
var miscDetails = details ? getPortalMiscDetails(guid,details) : '';
|
||
var resoDetails = details ? getResonatorDetails(details) : '';
|
||
|
||
//TODO? other status details...
|
||
var statusDetails = details ? '' : '<div id="portalStatus">Loading details...</div>';
|
||
|
||
|
||
var img = fixPortalImageUrl(details ? details.image : data.image);
|
||
var title = (details && details.title) || (data && data.title) || 'null';
|
||
|
||
var lat = data.latE6/1E6;
|
||
var lng = data.lngE6/1E6;
|
||
|
||
var imgTitle = title+'\n\nClick to show full image.';
|
||
|
||
|
||
// portal level. start with basic data - then extend with fractional info in tooltip if available
|
||
var levelInt = (teamStringToId(data.team) == TEAM_NONE) ? 0 : data.level;
|
||
var levelDetails = levelInt;
|
||
if (details) {
|
||
levelDetails = getPortalLevel(details);
|
||
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;
|
||
|
||
|
||
var linkDetails = [];
|
||
|
||
var posOnClick = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(title)+'\')';
|
||
var permalinkUrl = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;
|
||
|
||
if (typeof android !== 'undefined' && android && android.intentPosLink) {
|
||
// android devices. one share link option - and the android app provides an interface to share the URL,
|
||
// share as a geo: intent (navigation via google maps), etc
|
||
|
||
var shareLink = $('<div>').html( $('<a>').attr({onclick:posOnClick}).text('Share portal') ).html();
|
||
linkDetails.push('<aside>'+shareLink+'</aside>');
|
||
|
||
} else {
|
||
// non-android - a permalink for the portal
|
||
var permaHtml = $('<div>').html( $('<a>').attr({href:permalinkUrl, title:'Create a URL link to this portal'}).text('Portal link') ).html();
|
||
linkDetails.push ( '<aside>'+permaHtml+'</aside>' );
|
||
|
||
// and a map link popup dialog
|
||
var mapHtml = $('<div>').html( $('<a>').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html();
|
||
linkDetails.push('<aside>'+mapHtml+'</aside>');
|
||
|
||
}
|
||
|
||
$('#portaldetails')
|
||
.html('') //to ensure it's clear
|
||
.attr('class', TEAM_TO_CSS[teamStringToId(data.team)])
|
||
.append(
|
||
$('<h3>').attr({class:'title'}).text(title),
|
||
|
||
$('<span>').attr({
|
||
class: 'close',
|
||
title: 'Close [w]',
|
||
onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',
|
||
accesskey: 'w'
|
||
}).text('X'),
|
||
|
||
// help cursor via ".imgpreview img"
|
||
$('<div>')
|
||
.attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"})
|
||
.append(
|
||
$('<span>').attr({id:'level', title: levelDetails}).text(levelInt),
|
||
$('<img>').attr({class:'hide', src:img})
|
||
),
|
||
|
||
modDetails,
|
||
miscDetails,
|
||
resoDetails,
|
||
statusDetails,
|
||
'<div class="linkdetails">' + linkDetails.join('') + '</div>'
|
||
);
|
||
|
||
// only run the hooks when we have a portalDetails object - most plugins rely on the extended data
|
||
// TODO? another hook to call always, for any plugins that can work with less data?
|
||
if (details) {
|
||
runHooks('portalDetailsUpdated', {guid: guid, portal: portal, portalDetails: details, portalData: data});
|
||
}
|
||
}
|
||
|
||
|
||
|
||
window.getPortalMiscDetails = function(guid,d) {
|
||
|
||
var randDetails;
|
||
|
||
if (d) {
|
||
|
||
// collect some random data that’s not worth to put in an own method
|
||
var linkInfo = getPortalLinks(guid);
|
||
var maxOutgoing = getMaxOutgoingLinks(d);
|
||
var linkCount = linkInfo.in.length + linkInfo.out.length;
|
||
var links = {incoming: linkInfo.in.length, outgoing: linkInfo.out.length};
|
||
|
||
var title = 'at most ' + maxOutgoing + ' outgoing links\n' +
|
||
links.outgoing + ' links out\n' +
|
||
links.incoming + ' links in\n' +
|
||
'(' + (links.outgoing+links.incoming) + ' total)'
|
||
var linksText = ['links', links.outgoing+' out / '+links.incoming+' in', title];
|
||
|
||
var player = d.owner
|
||
? '<span class="nickname">' + d.owner + '</span>'
|
||
: '-';
|
||
var playerText = ['owner', player];
|
||
|
||
|
||
var fieldCount = getPortalFieldsCount(guid);
|
||
|
||
var fieldsText = ['fields', fieldCount];
|
||
|
||
var apGainText = getAttackApGainText(d,fieldCount,linkCount);
|
||
|
||
var attackValues = getPortalAttackValues(d);
|
||
|
||
|
||
// collect and html-ify random data
|
||
|
||
var randDetailsData = [
|
||
// these pieces of data are only relevant when the portal is captured
|
||
// maybe check if portal is captured and remove?
|
||
// But this makes the info panel look rather empty for unclaimed portals
|
||
playerText, getRangeText(d),
|
||
linksText, fieldsText,
|
||
getMitigationText(d,linkCount), getEnergyText(d),
|
||
// and these have some use, even for uncaptured portals
|
||
apGainText, getHackDetailsText(d),
|
||
];
|
||
|
||
if(attackValues.attack_frequency != 0)
|
||
randDetailsData.push([
|
||
'<span title="attack frequency" class="text-overflow-ellipsis">attack frequency</span>',
|
||
'×'+attackValues.attack_frequency]);
|
||
if(attackValues.hit_bonus != 0)
|
||
randDetailsData.push(['hit bonus', attackValues.hit_bonus+'%']);
|
||
if(attackValues.force_amplifier != 0)
|
||
randDetailsData.push([
|
||
'<span title="force amplifier" class="text-overflow-ellipsis">force amplifier</span>',
|
||
'×'+attackValues.force_amplifier]);
|
||
|
||
randDetails = '<table id="randdetails">' + genFourColumnTable(randDetailsData) + '</table>';
|
||
|
||
|
||
// artifacts - tacked on after (but not as part of) the 'randdetails' table
|
||
// instead of using the existing columns....
|
||
|
||
if (d.artifactBrief && d.artifactBrief.target && Object.keys(d.artifactBrief.target).length > 0) {
|
||
var targets = Object.keys(d.artifactBrief.target);
|
||
//currently (2015-07-10) we no longer know the team each target portal is for - so we'll just show the artifact type(s)
|
||
randDetails += '<div id="artifact_target">Target portal: '+targets.map(function(x) { return x.capitalize(); }).join(', ')+'</div>';
|
||
}
|
||
|
||
// shards - taken directly from the portal details
|
||
if (d.artifactDetail) {
|
||
randDetails += '<div id="artifact_fragments">Shards: '+d.artifactDetail.displayName+' #'+d.artifactDetail.fragments.join(', ')+'</div>';
|
||
}
|
||
|
||
}
|
||
|
||
return randDetails;
|
||
}
|
||
|
||
|
||
// draws link-range and hack-range circles around the portal with the
|
||
// given details. Clear them if parameter 'd' is null.
|
||
window.setPortalIndicators = function(p) {
|
||
|
||
if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
|
||
portalRangeIndicator = null;
|
||
if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
|
||
portalAccessIndicator = null;
|
||
|
||
// if we have a portal...
|
||
|
||
if(p) {
|
||
var coord = p.getLatLng();
|
||
|
||
// range is only known for sure if we have portal details
|
||
// TODO? render a min range guess until details are loaded..?
|
||
|
||
var d = portalDetail.get(p.options.guid);
|
||
if (d) {
|
||
var range = getPortalRange(d);
|
||
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.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);
|
||
|
||
runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
|
||
return update;
|
||
}
|