diff --git a/core/code/map_data_render.js b/core/code/map_data_render.js index 62a5f79ba..fc1c423d9 100644 --- a/core/code/map_data_render.js +++ b/core/code/map_data_render.js @@ -46,7 +46,9 @@ window.Render.prototype.clearPortalsOutsideBounds = function (bounds) { var p = window.portals[guid]; // clear portals outside visible bounds - unless it's the selected portal, or it's relevant to artifacts if (!bounds.contains(p.getLatLng()) && guid !== window.selectedPortal && !window.artifact.isInterestingPortal(guid)) { - this.deletePortalEntity(guid); + // remove the marker as a layer first + // deletion will be done at endRenderPass + p.remove(); } } }; @@ -208,10 +210,7 @@ window.Render.prototype.endRenderPass = function () { // reorder portals to be after links/fields this.bringPortalsToFront(); - // re-select the selected portal, to re-render the side-bar. ensures that any data calculated from the map data is up to date - if (window.selectedPortal) { - window.renderPortalDetails(window.selectedPortal); - } + this.isRendering = false; }; /** @@ -305,6 +304,7 @@ window.Render.prototype.deleteFieldEntity = function (guid) { * @param {number} latE6 - The latitude of the portal in E6 format. * @param {number} lngE6 - The longitude of the portal in E6 format. * @param {string} team - The team faction of the portal. + * @param {number} [timestamp=0] - Timestamp of the portal data. Defaults to 0 to allow newer data sources to override * @param {number} [timestamp] - The timestamp of the portal data. */ window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, lngE6, team, timestamp) { @@ -319,7 +319,7 @@ window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, l var ent = [ guid, // ent[0] = guid - -1, // ent[1] = timestamp - zero will mean any other source of portal data will have a higher timestamp + timestamp, // ent[1] = timestamp // ent[2] = an array with the entity data [ 'p', // 0 - a portal @@ -329,22 +329,7 @@ window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, l ], ]; - // placeholder portals don't have a useful timestamp value - so the standard code that checks for updated - // portal details doesn't apply - // so, check that the basic details are valid and delete the existing portal if out of date - var portalMoved = false; - if (guid in window.portals) { - var p = window.portals[guid]; - portalMoved = latE6 !== p.options.data.latE6 || lngE6 !== p.options.data.lngE6; - if (team !== p.options.data.team && p.options.timestamp < timestamp) { - // team - delete existing portal - this.deletePortalEntity(guid); - } - } - - if (!portalMoved) { - this.createPortalEntity(ent, 'core'); // placeholder - } + this.createPortalEntity(ent, 'core'); // placeholder }; /** @@ -362,102 +347,95 @@ window.Render.prototype.createPortalEntity = function (ent, details) { var previousData = undefined; var data = window.decodeArray.portal(ent[2], details); + var guid = ent[0]; + + // add missing fields + data.guid = guid; + if (!data.timestamp) { + data.timestamp = ent[1]; + } + + // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead + data.ent = ent; // check if entity already exists - if (ent[0] in window.portals) { + const oldPortal = guid in window.portals; + + if (oldPortal) { // yes. now check to see if the entity data we have is newer than that in place - var p = window.portals[ent[0]]; + var p = window.portals[guid]; - if (!data.history || p.options.data.history === data.history) - if (p.options.timestamp >= ent[1]) { - return; // this data is identical or older - abort processing - } + if (!p.willUpdate(data)) { + // this data doesn't bring new detail - abort processing + // re-add the portal to the relevant layer (does nothing if already in the correct layer) + // useful for portals outside the view + this.addPortalToMapLayer(p); + return p; + } // the data we have is newer. many data changes require re-rendering of the portal // (e.g. level changed, so size is different, or stats changed so highlighter is different) - // so to keep things simple we'll always re-create the entity in this case // remember the old details, for the callback - - previousData = p.options.data; - - // preserve history - if (!data.history) { - data.history = previousData.history; - } - - this.deletePortalEntity(ent[0]); + previousData = $.extend(true, {}, p.getDetails()); } - var portalLevel = parseInt(data.level) || 0; - var team = window.teamStringToId(data.team); - // the data returns unclaimed portals as level 1 - but IITC wants them treated as level 0 - if (team === window.TEAM_NONE) portalLevel = 0; - var latlng = L.latLng(data.latE6 / 1e6, data.lngE6 / 1e6); - var dataOptions = { - level: portalLevel, - team: team, - ent: ent, // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead - guid: ent[0], - timestamp: ent[1], - data: data, - }; - - window.pushPortalGuidPositionCache(ent[0], data.latE6, data.lngE6); - - var marker = window.createMarker(latlng, dataOptions); - - function handler_portal_click(e) { - window.renderPortalDetails(e.target.options.guid); - } - function handler_portal_dblclick(e) { - window.renderPortalDetails(e.target.options.guid); - window.map.setView(e.target.getLatLng(), window.DEFAULT_ZOOM); - } - function handler_portal_contextmenu(e) { - window.renderPortalDetails(e.target.options.guid); - if (window.isSmartphone()) { - window.show('info'); - } else if (!$('#scrollwrapper').is(':visible')) { - $('#sidebartoggle').click(); - } - } - - marker.on('click', handler_portal_click); - marker.on('dblclick', handler_portal_dblclick); - marker.on('contextmenu', handler_portal_contextmenu); - - window.runHooks('portalAdded', { portal: marker, previousData: previousData }); - - window.portals[ent[0]] = marker; + window.pushPortalGuidPositionCache(data.guid, data.latE6, data.lngE6); // check for URL links to portal, and select it if this is the one - if (window.urlPortalLL && window.urlPortalLL[0] === marker.getLatLng().lat && window.urlPortalLL[1] === marker.getLatLng().lng) { + if (window.urlPortalLL && window.urlPortalLL[0] === latlng.lat && window.urlPortalLL[1] === latlng.lng) { // URL-passed portal found via pll parameter - set the guid-based parameter - log.log('urlPortalLL ' + window.urlPortalLL[0] + ',' + window.urlPortalLL[1] + ' matches portal GUID ' + ent[0]); + log.log('urlPortalLL ' + window.urlPortalLL[0] + ',' + window.urlPortalLL[1] + ' matches portal GUID ' + data.guid); - window.urlPortal = ent[0]; + window.urlPortal = data.guid; window.urlPortalLL = undefined; // clear the URL parameter so it's not matched again } - if (window.urlPortal === ent[0]) { + if (window.urlPortal === data.guid) { // URL-passed portal found via guid parameter - set it as the selected portal log.log('urlPortal GUID ' + window.urlPortal + ' found - selecting...'); - window.selectedPortal = ent[0]; + window.selectedPortal = data.guid; window.urlPortal = undefined; // clear the URL parameter so it's not matched again } - // (re-)select the portal, to refresh the sidebar on any changes - if (ent[0] === window.selectedPortal) { - log.log('portal guid ' + ent[0] + ' is the selected portal - re-rendering portal details'); - window.renderPortalDetails(window.selectedPortal); + let marker = undefined; + if (oldPortal) { + // update marker style/highlight and layer + marker = window.portals[data.guid]; + + marker.updateDetails(data); + + window.runHooks('portalAdded', { portal: marker, previousData: previousData }); + } else { + marker = window.createMarker(latlng, data); + + // in case of incomplete data while having fresh details in cache, update the portal with those details + if (window.portalDetail.isFresh(guid)) { + var oldDetails = window.portalDetail.get(guid); + if (data.timestamp > oldDetails.timestamp) { + // data is more recent than the cached details so we remove them from the cache + window.portalDetail.remove(guid); + } else if (marker.willUpdate(oldDetails)) { + marker.updateDetails(oldDetails); + } + } + + window.runHooks('portalAdded', { portal: marker }); + + window.portals[data.guid] = marker; + + if (window.selectedPortal === data.guid) { + marker.renderDetails(); + } } window.ornaments.addPortal(marker); // TODO? postpone adding to the map layer this.addPortalToMapLayer(marker); + + return marker; }; /** @@ -482,7 +460,7 @@ window.Render.prototype.createFieldEntity = function (ent) { // create placeholder portals for field corners. we already do links, but there are the odd case where this is useful for (var i = 0; i < 3; i++) { var p = data.points[i]; - this.createPlaceholderPortalEntity(p.guid, p.latE6, p.lngE6, data.team, data.timestamp); + this.createPlaceholderPortalEntity(p.guid, p.latE6, p.lngE6, data.team, 0); } // check if entity already exists diff --git a/core/code/map_data_request.js b/core/code/map_data_request.js index 1402c4235..91a1073c1 100644 --- a/core/code/map_data_request.js +++ b/core/code/map_data_request.js @@ -65,14 +65,6 @@ window.MapDataRequest = function () { // ensure we have some initial map status this.setStatus('startup', undefined, -1); - - // add a portalDetailLoaded hook, so we can use the extended details to update portals on the map - var _this = this; - window.addHook('portalDetailLoaded', function (data) { - if (data.success) { - _this.render.createPortalEntity(data.ent, 'detailed'); - } - }); }; /** diff --git a/core/code/portal_detail.js b/core/code/portal_detail.js index 39f25db6e..aa38386ef 100644 --- a/core/code/portal_detail.js +++ b/core/code/portal_detail.js @@ -54,27 +54,24 @@ window.portalDetail.isFresh = function (guid) { return cache.isFresh(guid); }; +window.portalDetail.remove = function (guid) { + return cache.remove(guid); +}; + var handleResponse = function (deferred, guid, data, success) { if (!data || data.error || !data.result) { success = false; } if (success) { - var dict = window.decodeArray.portal(data.result, 'detailed'); - // entity format, as used in map data - var ent = [guid, dict.timestamp, data.result]; + var ent = [guid, data.result[13], data.result]; + var portal = window.mapDataRequest.render.createPortalEntity(ent, 'detailed'); - cache.store(guid, dict); - - // FIXME..? better way of handling sidebar refreshing... - - if (guid === window.selectedPortal) { - window.renderPortalDetails(guid); - } + cache.store(guid, portal.options.data); - deferred.resolve(dict); - window.runHooks('portalDetailLoaded', { guid: guid, success: success, details: dict, ent: ent }); + deferred.resolve(portal.options.data); + window.runHooks('portalDetailLoaded', { guid: guid, success: success, details: portal.options.data, ent: ent }); } else { if (data && data.error === 'RETRY') { // server asked us to try again diff --git a/core/code/portal_detail_display.js b/core/code/portal_detail_display.js index d58662943..9c2489b7e 100644 --- a/core/code/portal_detail_display.js +++ b/core/code/portal_detail_display.js @@ -69,9 +69,12 @@ window.renderPortalUrl = function (lat, lng, title, guid) { * * @function renderPortalDetails * @param {string|null} guid - The globally unique identifier of the portal to display details for. + * @param {boolean} [forceSelect=false] - If true, forces the portal to be selected even if it's already the current portal. */ -window.renderPortalDetails = function (guid) { - window.selectPortal(window.portals[guid] ? guid : null); +window.renderPortalDetails = function (guid, forceSelect) { + if (forceSelect || window.selectedPortal !== guid) { + window.selectPortal(window.portals[guid] ? guid : null, 'renderPortalDetails'); + } if ($('#sidebar').is(':visible')) { window.resetScrollOnNewPortal(); window.renderPortalDetails.lastVisible = guid; @@ -95,34 +98,29 @@ window.renderPortalDetails = function (guid) { } var portal = window.portals[guid]; - var data = portal.options.data; - var details = window.portalDetail.get(guid); - var historyDetails = window.getPortalHistoryDetails(data); - - // details and data can get out of sync. if we have details, construct a matching 'data' - if (details) { - data = window.getPortalSummaryData(details); - } + var details = portal.getDetails(); + var hasFullDetails = portal.hasFullDetails(); + var historyDetails = window.getPortalHistoryDetails(details); - var modDetails = details ? '
' + window.getModDetails(details) + '
' : ''; - var miscDetails = details ? window.getPortalMiscDetails(guid, details) : ''; - var resoDetails = details ? window.getResonatorDetails(details) : ''; + var modDetails = hasFullDetails ? '
' + window.getModDetails(details) + '
' : ''; + var miscDetails = hasFullDetails ? window.getPortalMiscDetails(guid, details) : ''; + var resoDetails = hasFullDetails ? window.getResonatorDetails(details) : ''; // TODO? other status details... - var statusDetails = details ? '' : '
Loading details...
'; + var statusDetails = hasFullDetails ? '' : '
Loading details...
'; - var img = window.fixPortalImageUrl(details ? details.image : data.image); - var title = (details && details.title) || (data && data.title) || 'null'; + var img = window.fixPortalImageUrl(details.image); + var title = details.title || 'null'; - var lat = data.latE6 / 1e6; - var lng = data.lngE6 / 1e6; + var lat = details.latE6 / 1e6; + var lng = details.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 = window.teamStringToId(data.team) === window.TEAM_NONE ? 0 : data.level; + var levelInt = portal.options.level; var levelDetails = levelInt; - if (details) { + if (hasFullDetails) { levelDetails = window.getPortalLevel(details); if (levelDetails !== 8) { if (levelDetails === Math.ceil(levelDetails)) levelDetails += '\n8'; @@ -136,7 +134,7 @@ window.renderPortalDetails = function (guid) { $('#portaldetails') .html('') // to ensure it's clear - .attr('class', window.TEAM_TO_CSS[window.teamStringToId(data.team)]) + .attr('class', window.TEAM_TO_CSS[window.teamStringToId(details.team)]) .append( $('

', { class: 'title' }) .text(title) @@ -147,7 +145,7 @@ window.renderPortalDetails = function (guid) { style: 'float: left', }) .click(function () { - window.zoomToAndShowPortal(guid, [data.latE6 / 1e6, data.lngE6 / 1e6]); + window.zoomToAndShowPortal(guid, [details.latE6 / 1e6, details.lngE6 / 1e6]); if (window.isSmartphone()) { window.show('map'); } @@ -187,9 +185,12 @@ window.renderPortalDetails = function (guid) { window.renderPortalUrl(lat, lng, title, guid); + // compatibility + var data = hasFullDetails ? window.getPortalSummaryData(details) : details; + // 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) { + if (hasFullDetails) { window.runHooks('portalDetailsUpdated', { guid: guid, portal: portal, portalDetails: details, portalData: data }); } }; @@ -337,7 +338,7 @@ window.setPortalIndicators = function (p) { * @param {string} guid - The GUID of the portal to select. * @returns {boolean} True if the same portal is re-selected (just an update), false if a different portal is selected. */ -window.selectPortal = function (guid) { +window.selectPortal = function (guid, event) { var update = window.selectedPortal === guid; var oldPortalGuid = window.selectedPortal; window.selectedPortal = guid; @@ -346,20 +347,18 @@ window.selectPortal = function (guid) { var newPortal = window.portals[guid]; // Restore style of unselected portal - if (!update && oldPortal) window.setMarkerStyle(oldPortal, false); + if (!update && oldPortal) oldPortal.setSelected(false); // Change style of selected portal - if (newPortal) { - window.setMarkerStyle(newPortal, true); - - if (window.map.hasLayer(newPortal)) { - newPortal.bringToFront(); - } - } + if (newPortal) newPortal.setSelected(true); window.setPortalIndicators(newPortal); - window.runHooks('portalSelected', { selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid }); + window.runHooks('portalSelected', { + selectedPortalGuid: guid, + unselectedPortalGuid: oldPortalGuid, + event: event, + }); return update; }; diff --git a/core/code/portal_highlighter.js b/core/code/portal_highlighter.js index a55b1b138..c40f6c8bb 100644 --- a/core/code/portal_highlighter.js +++ b/core/code/portal_highlighter.js @@ -113,7 +113,7 @@ window.changePortalHighlights = function (name) { */ window.highlightPortal = function (p) { if (window._highlighters !== null && window._highlighters[window._current_highlighter] !== undefined) { - window._highlighters[window._current_highlighter].highlight({ portal: p }); + return window._highlighters[window._current_highlighter].highlight({ portal: p }); } }; diff --git a/core/code/portal_info.js b/core/code/portal_info.js index 6cd62b16b..9a210901f 100644 --- a/core/code/portal_info.js +++ b/core/code/portal_info.js @@ -59,6 +59,21 @@ window.getCurrentPortalEnergy = function (d) { return nrg; }; +/** + * Calculates the health percentage of a portal based on its current and total energy. + * + * @function getPortalHealth + * @param {Object} d - The portal detail object containing resonator information. + * @returns {number} The portal health as a percentage (0-100). + * Returns 0 if the portal has no total energy. + */ +window.getPortalHealth = function (d) { + var max = window.getTotalPortalEnergy(d); + var cur = window.getCurrentPortalEnergy(d); + + return max > 0 ? Math.floor((cur / max) * 100) : 0; +}; + /** * Calculates the range of a portal for creating links. The range depends on portal level and any installed Link Amps. * diff --git a/core/code/portal_marker.js b/core/code/portal_marker.js index 0ad3f5192..4a2abf4d9 100644 --- a/core/code/portal_marker.js +++ b/core/code/portal_marker.js @@ -1,10 +1,282 @@ -/* global L -- eslint */ +/* global IITC, L, log -- eslint */ /** * @file This file contains the code related to creating and updating portal markers on the map. * @module portal_marker */ +// portal hooks +function handler_portal_click(e) { + window.selectPortal(e.target.options.guid, e.type); + window.renderPortalDetails(e.target.options.guid); +} + +function handler_portal_dblclick(e) { + window.selectPortal(e.target.options.guid, e.type); + window.renderPortalDetails(e.target.options.guid); + window.map.setView(e.target.getLatLng(), window.DEFAULT_ZOOM); +} + +function handler_portal_contextmenu(e) { + window.selectPortal(e.target.options.guid, e.type); + window.renderPortalDetails(e.target.options.guid); + if (window.isSmartphone()) { + window.show('info'); + } else if (!$('#scrollwrapper').is(':visible')) { + $('#sidebartoggle').click(); + } +} + +L.PortalMarker = L.CircleMarker.extend({ + options: {}, + + statics: { + // base style + portalBaseStyle: { + stroke: true, + opacity: 1, + fill: true, + fillOpacity: 0.5, + interactive: true, + }, + // placeholder style + placeholderStyle: { + dashArray: '1,2', + weight: 1, + }, + // portal level 0 1 2 3 4 5 6 7 8 + LEVEL_TO_WEIGHT: [2, 2, 2, 2, 2, 3, 3, 4, 4], + LEVEL_TO_RADIUS: [7, 7, 7, 7, 8, 8, 9, 10, 11], + }, + + initialize: function (latlng, data) { + L.CircleMarker.prototype.initialize.call(this, latlng); + this._selected = data.guid === window.selectedPortal; + this.updateDetails(data); + + this.on('click', handler_portal_click); + this.on('dblclick', handler_portal_dblclick); + this.on('contextmenu', handler_portal_contextmenu); + }, + + willUpdate: function (details) { + // details are from a placeholder + if (details.level === undefined) { + // if team differs and corresponding link is more recent (ignore field) + return this._details.timestamp < details.timestamp && this._details.team !== details.team; + } + // more recent timestamp, this occurs when the data has changed because of: + // - resonator deploy/upgrade + // - mod deploy + // - recharge/damage/decay + // - portal edit (title, location, portal main picture) + if (this._details.timestamp < details.timestamp) { + return true; + } + // current marker is a placeholder, and details is real data + if (this.isPlaceholder() && this._details.team === details.team) { + return true; + } + // even if we get history that was missing ? is it even possible ? + if (this._details.timestamp > details.timestamp) { + return false; + } + + // this._details.timestamp === details.timestamp + + // get new history + if (details.history) { + if (!this._details.history) { + return true; + } + if (this._details.history._raw !== details.history._raw) { + return true; + } + } + + // get details portal data + if (!this._details.mods && details.mods) { + return true; + } + + return false; + }, + + updateDetails: function (details) { + if (this._details) { + // portal has been moved + if (this._details.latE6 !== details.latE6 || this._details.lngE6 !== details.lngE6) { + this.setLatLng(L.latLng(details.latE6 / 1e6, details.lngE6 / 1e6)); + } + + // core data from a placeholder + if (details.level === undefined) { + // if team has changed + if (this._details.timestamp < details.timestamp && this._details.team !== details.team) { + // keep history, title, image + details.title = this._details.title; + details.image = this._details.image; + details.history = this._details.history; + this._details = details; + } + } else if (this._details.timestamp === details.timestamp) { + // we got more details (core/summary -> summary/detailed/extended) + var localThis = this; + [ + 'level', + 'health', + 'resCount', + 'image', + 'title', + 'ornaments', + 'mission', + 'mission50plus', + 'artifactBrief', + 'mods', + 'resonators', + 'owner', + 'artifactDetail', + ].forEach(function (prop) { + if (details[prop]) localThis._details[prop] = details[prop]; + }); + // smarter update for history (cause it's missing sometimes) + if (details.history) { + if (!this._details.history) { + this._details.history = details.history; + } else { + if (this._details.history._raw && details.history._raw !== this._details.history._raw) { + log.warn('new portal data has lost some history'); + } + this._details.history._raw |= details.history._raw; + ['visited', 'captured', 'scoutControlled'].forEach(function (prop) { + localThis._details.history[prop] ||= details.history[prop]; + }); + } + } + // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead + this._details.ent = details.ent; + } else { + // permanent data (history only) + if (!details.history) { + details.history = this._details.history; + } + + this._details = details; + } + } else { + this._details = details; + } + + this._level = parseInt(this._details.level) || 0; + this._team = IITC.utils.getTeamId(this._details.team); + + // the data returns unclaimed portals as level 1 - but IITC wants them treated as level 0 + if (this._team === window.TEAM_NONE) { + this._level = 0; + } + + // compatibility + var dataOptions = { + guid: this._details.guid, + level: this._level, + team: this._team, + ent: this._details.ent, // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead + timestamp: this._details.timestamp, + data: this._details, + }; + L.setOptions(this, dataOptions); + + if (this._selected) { + this.renderDetails(); + } + + this.setSelected(); + }, + + renderDetails() { + if (!this._rendering) { + this._rendering = true; + window.renderPortalDetails(this._details.guid); + this._rendering = false; + } + }, + + getDetails: function () { + return this._details; + }, + + isPlaceholder: function () { + return this._details.level === undefined; + }, + + hasFullDetails: function () { + return !!this._details.mods; + }, + + setStyle: function (style) { + // stub for highlighters + L.Util.setOptions(this, style); + return this; + }, + + setMarkerStyle: function (style) { + var styleOptions = L.Util.extend(this._style(), style); + L.Util.setOptions(this, styleOptions); + + L.Util.setOptions(this, window.highlightPortal(this)); + + var selected = L.extend({ radius: this.options.radius }, this._selected && { color: window.COLOR_SELECTED_PORTAL }); + return L.CircleMarker.prototype.setStyle.call(this, selected); + }, + + setSelected: function (selected) { + if (selected === false) { + this._selected = false; + } else { + this._selected = this._selected || selected; + } + + this.setMarkerStyle(); + + if (this._selected && window.map.hasLayer(this)) { + this.bringToFront(); + } + }, + + _style: function () { + var dashArray = null; + // dashed outline for placeholder portals + if (this.isPlaceholder()) { + dashArray = L.PortalMarker.placeholderStyle.dashArray; + } + + return L.extend(this._scale(), L.PortalMarker.portalBaseStyle, { + color: window.COLORS[this._team], + fillColor: window.COLORS[this._team], + dashArray: dashArray, + }); + }, + + _scale: function () { + var scale = window.portalMarkerScale(); + + var level = Math.floor(this._level || 0); + + var lvlWeight = L.PortalMarker.LEVEL_TO_WEIGHT[level] * Math.sqrt(scale); + var lvlRadius = L.PortalMarker.LEVEL_TO_RADIUS[level] * scale; + + // thinner outline for placeholder portals + if (this.isPlaceholder()) { + lvlWeight = L.PortalMarker.placeholderStyle.weight; + } + + return { + radius: lvlRadius, + weight: lvlWeight, + }; + }, +}); + /** * Calculates the scale of portal markers based on the current zoom level of the map. * @@ -23,39 +295,21 @@ window.portalMarkerScale = function () { * @function createMarker * @param {L.LatLng} latlng - The latitude and longitude where the marker will be placed. * @param {Object} data - The IITC-specific entity data to be stored in the marker options. - * @returns {L.circleMarker} A Leaflet circle marker representing the portal. + * @returns {L.PortalMarker} A Leaflet circle marker representing the portal. */ window.createMarker = function (latlng, data) { - var styleOptions = window.getMarkerStyleOptions(data); - - var options = L.extend({}, data, styleOptions, { interactive: true }); - - var marker = L.circleMarker(latlng, options); - - window.highlightPortal(marker); - - return marker; + return new L.PortalMarker(latlng, data); }; /** * Sets the style of a portal marker, including options for when the portal is selected. * * @function setMarkerStyle - * @param {L.circleMarker} marker - The portal marker whose style will be set. + * @param {L.PortalMarker} marker - The portal marker whose style will be set. * @param {boolean} selected - Indicates if the portal is selected. */ window.setMarkerStyle = function (marker, selected) { - var styleOptions = window.getMarkerStyleOptions(marker.options); - - marker.setStyle(styleOptions); - - // FIXME? it's inefficient to set the marker style (above), then do it again inside the highlighter - // the highlighter API would need to be changed for this to be improved though. will it be too slow? - window.highlightPortal(marker); - - if (selected) { - marker.setStyle({ color: window.COLOR_SELECTED_PORTAL }); - } + marker.setSelected(selected); }; /** @@ -68,33 +322,30 @@ window.setMarkerStyle = function (marker, selected) { window.getMarkerStyleOptions = function (details) { var scale = window.portalMarkerScale(); - // portal level 0 1 2 3 4 5 6 7 8 - var LEVEL_TO_WEIGHT = [2, 2, 2, 2, 2, 3, 3, 4, 4]; - var LEVEL_TO_RADIUS = [7, 7, 7, 7, 8, 8, 9, 10, 11]; - var level = Math.floor(details.level || 0); - var lvlWeight = LEVEL_TO_WEIGHT[level] * Math.sqrt(scale); - var lvlRadius = LEVEL_TO_RADIUS[level] * scale; + var lvlWeight = L.PortalMarker.LEVEL_TO_WEIGHT[level] * Math.sqrt(scale); + var lvlRadius = L.PortalMarker.LEVEL_TO_RADIUS[level] * scale; var dashArray = null; // thinner and dashed outline for placeholder portals if (details.team !== window.TEAM_NONE && level === 0) { - lvlWeight = 1; - dashArray = '1,2'; + lvlWeight = L.PortalMarker.placeholderStyle.weight; + dashArray = L.PortalMarker.placeholderStyle.dashArray; } - var options = { - radius: lvlRadius, - stroke: true, - color: window.COLORS[details.team], - weight: lvlWeight, - opacity: 1, - fill: true, - fillColor: window.COLORS[details.team], - fillOpacity: 0.5, - dashArray: dashArray, - }; + var options = L.extend( + { + radius: lvlRadius, + weight: lvlWeight, + }, + L.PortalMarker.portalBaseStyle, + { + color: window.COLORS[details.team], + fillColor: window.COLORS[details.team], + dashArray: dashArray, + } + ); return options; };