// 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('
tap here for info screen
'); } 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 ? '
'+getModDetails(details)+'
' : ''; var miscDetails = details ? getPortalMiscDetails(guid,details) : ''; var resoDetails = details ? getResonatorDetails(details) : ''; //TODO? other status details... var statusDetails = details ? '' : '
Loading details...
'; var img = fixPortalImageUrl(details ? details.image : data.image); var title = data.title; var lat = data.latE6/1E6; var lng = data.lngE6/1E6; var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title; imgTitle += '\n\nClick to show full image.'; var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined; var portalDetailedDescription = ''; if(portalDetailObj) { portalDetailedDescription = ''; // 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 = ''; } else { submitterSpan = ''; } portalDetailedDescription += ''; } if(portalDetailObj.submitter.link.length > 0) { portalDetailedDescription += ''; } if(portalDetailObj.description) { portalDetailedDescription += ''; } // if(d.descriptiveText.map.ADDRESS) { // portalDetailedDescription += ''; // } portalDetailedDescription += '
Photo by:' + submitterSpan + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
Photo from:' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
Description:' + escapeHtmlSpecialChars(portalDetailObj.description) + '
Address:' + escapeHtmlSpecialChars(d.descriptiveText.map.ADDRESS) + '
'; } // 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 = $('
').html( $('').attr({onclick:posOnClick}).text('Share portal') ).html(); linkDetails.push(''); } else { // non-android - a permalink for the portal var permaHtml = $('
').html( $('').attr({href:permalinkUrl, title:'Create a URL link to this portal'}).text('Portal link') ).html(); linkDetails.push ( '' ); // and a map link popup dialog var mapHtml = $('
').html( $('').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html(); linkDetails.push(''); } $('#portaldetails') .html('') //to ensure it's clear .attr('class', TEAM_TO_CSS[teamStringToId(data.team)]) .append( $('

').attr({class:'title'}).text(data.title), $('').attr({class:'close', onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',title:'Close'}).text('X'), // help cursor via ".imgpreview img" $('
') .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"}) .append( $('').attr({id:'level', title: levelDetails}).text(levelInt), $('
').attr({class:'portalDetails'}).html(portalDetailedDescription), $('').attr({class:'hide', src:img}) ), modDetails, miscDetails, resoDetails, statusDetails, '
' + linkDetails.join('') + '
' ); // 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 links = {incoming: linkInfo.in.length, outgoing: linkInfo.out.length}; function linkExpl(t) { return ''+t+''; } var linksText = [linkExpl('links'), linkExpl(links.outgoing+' out / '+links.incoming+' in')]; var player = d.owner ? '' + d.owner + '' : null; var playerText = player ? ['owner', player] : null; var time = d.capturedTime ? '' + unixTimeToString(d.capturedTime) + '' : null; var sinceText = time ? ['since', time] : null; var fieldCount = getPortalFieldsCount(guid); var linkedFields = ['fields', fieldCount]; var linkCount = getPortalLinksCount(guid); // collect and html-ify random data var randDetailsData = []; if (playerText && sinceText) { randDetailsData.push (playerText, sinceText); } randDetailsData.push ( getRangeText(d), getEnergyText(d), linksText, ['-','-'], linkedFields, getAttackApGainText(d,fieldCount,linkCount), getHackDetailsText(d), getMitigationText(d,linkCount) ); // artifact details // 2014-02-06: stock site changed from supporting 'jarvis shards' to 'amar artifacts'(?) - so let's see what we can do to be generic... $.each(artifact.getArtifactTypes(),function(index,type) { var artdata = artifact.getPortalData (guid, type); if (artdata) { var details = artifact.getArtifactDescriptions(type); if (details) { // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns var target = ['',''], shards = [details.fragmentName,'(none)']; if (artdata.target) { target = ['target', ''+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+'']; } if (artdata.fragments) { shards = [details.fragmentName, '#'+artdata.fragments.join(', #')]; } randDetailsData.push (target, shards); } else { console.warn('Unknown artifact type '+type+': no names, so cannot display'); } } }); randDetails = '' + genFourColumnTable(randDetailsData) + '
'; } 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; }